Warning, /HLTrigger/Configuration/scripts/hltPhase2UpgradeIntegrationTests is written in an unsupported language. File is not indexed.
0001 #!/usr/bin/env python3
0002 import argparse
0003 import fnmatch
0004 import os
0005 import re
0006 import shutil
0007 import subprocess
0008 import sys
0009 import time
0010 from datetime import datetime
0011 from concurrent.futures import ThreadPoolExecutor, as_completed
0012 import Configuration.Geometry.defaultPhase2ConditionsEra_cff as _settings
0013 _PH2_GLOBAL_TAG, _PH2_ERA = _settings.get_era_and_conditions(_settings.DEFAULT_VERSION)
0014
0015 # Automatically generate the default geometry from DEFAULT_VERSION
0016 _PH2_GEOMETRY = f"Extended{_settings.DEFAULT_VERSION}"
0017
0018 # Get the actual era name from the version key
0019 _PH2_ERA_NAME = _settings.properties[2026][_settings.DEFAULT_VERSION]['Era']
0020
0021 # Function to display help information
0022 def print_help():
0023 help_text = """
0024 This script runs HLT test configurations for the CMS Phase2 upgrade.
0025
0026 Arguments:
0027 --globaltag : GlobalTag for the CMS conditions (required)
0028 --geometry : Geometry setting for the CMS process (required)
0029 --events : Number of events to process (default: 1)
0030 --threads : Number of threads to use (default: 1)
0031 --parallelJobs : Number of parallel cmsRun and hltDiff executions (default: 4)
0032 --restrictPathsTo : Restrict paths to be run to a user defined subset (e.g. "HLT_Ele*")
0033 --procModifiers : Optionally use one (or more) cmssw processModifier
0034 --cachedInput : Optionally use an existing RAW data file (do not regenerate it from scratch)
0035
0036 Example usage:
0037 hltPhase2UpgradeIntegrationTests --globaltag auto:phase2_realistic_T33 --geometry Extended2026D110 --events 10 --threads 4
0038 """
0039 print(help_text)
0040
0041 # Function to run a shell command and handle errors
0042 def run_command(command, log_file=None, workdir=None):
0043 try:
0044 print(f"Running command: {command}")
0045 with open(log_file, "w") as log:
0046 subprocess.run(command, shell=True, check=True, cwd=workdir, stdout=log, stderr=log)
0047 except subprocess.CalledProcessError as e:
0048 print(f"Error running command: {e}")
0049 return e.returncode # Return the error code
0050
0051 return 0 # Return 0 for success
0052
0053 # Function to compare the single file HLT results with the respect to a the base
0054 def compare_single_file(root_file, base_root_file, num_events, output_dir):
0055 root_path = os.path.join(output_dir, root_file)
0056 print(f"Comparing {root_path} with {base_root_file} using hltDiff...")
0057
0058 # Run the hltDiff command
0059 hlt_diff_command = f"hltDiff -o {base_root_file} -n {root_path}"
0060 result = subprocess.run(hlt_diff_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
0061
0062 # Decode and process the output
0063 output = result.stdout.decode("utf-8")
0064 print(output) # Print for debug purposes
0065
0066 # Use a dynamic check based on the number of events configured
0067 expected_match_string = f"Found {num_events} matching events, out of which 0 have different HLT results"
0068
0069 # Check if the output contains the expected match string
0070 if expected_match_string not in output:
0071 return f"Error: {root_file} has different HLT results!"
0072
0073 return None # Return None if no issues are found
0074
0075 # Argument Parser for command-line configuration
0076 parser = argparse.ArgumentParser(description="Run HLT Test Configurations")
0077 parser.add_argument("--globaltag", default=_PH2_GLOBAL_TAG, help="GlobalTag for the CMS conditions")
0078 parser.add_argument("--geometry", default=_PH2_GEOMETRY, help="Geometry setting for the CMS process") # Auto-generated geometry default
0079 parser.add_argument("--era", default=_PH2_ERA_NAME, help="Era setting for the CMS process") # Convert _PH2_ERA to string
0080 parser.add_argument("--events", type=int, default=10, help="Number of events to process")
0081 parser.add_argument("--parallelJobs", type=int, default=4, help="Number of parallel cmsRun HLT jobs")
0082 parser.add_argument("--threads", type=int, default=1, help="Number of threads to use")
0083 parser.add_argument("--restrictPathsTo", nargs='+', default=[], help="List of HLT paths to restrict to")
0084 parser.add_argument("--cachedInput", default=None, help="Predefined input file to use instead of running TTbar GEN,SIM step")
0085 parser.add_argument("--procModifiers", default=None, help="Optional process modifier for cmsDriver") # New argument for procModifiers
0086
0087 # Step 0: Capture the start time and print the start timestamp
0088 start_time = time.time()
0089 start_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
0090 print("------------------------------------------------------------")
0091 print(f"Script started at {start_timestamp}")
0092 print("------------------------------------------------------------")
0093
0094 # Parse arguments
0095 try:
0096 args = parser.parse_args()
0097 except SystemExit:
0098 print_help()
0099 sys.exit(0)
0100
0101 global_tag = args.globaltag
0102 era = args.era
0103 geometry = args.geometry
0104 num_events = args.events
0105 num_threads = args.threads
0106 num_parallel_jobs = args.parallelJobs
0107 restrict_paths_to = args.restrictPathsTo
0108 proc_modifiers = args.procModifiers # Store the procModifiers option
0109
0110 # Print the values in a nice formatted manner
0111 print(f"{'Configuration Summary':^40}")
0112 print("=" * 40)
0113 print(f"Global Tag: {global_tag}")
0114 print(f"Geometry: {geometry}")
0115 print(f"Era: {era}")
0116 print(f"Num Events: {num_events}")
0117 print(f"Num Threads: {num_threads}")
0118 print(f"Num Parallel Jobs: {num_parallel_jobs}")
0119 # Print restrictPathsTo if provided
0120 if restrict_paths_to:
0121 print(f"Restricting paths to: {', '.join(restrict_paths_to)}")
0122 # Print procModifiers if provided
0123 if proc_modifiers:
0124 print(f"Proc Modifiers: {proc_modifiers}")
0125 if args.cachedInput:
0126 print(f"Using cached input file: {args.cachedInput}")
0127 else:
0128 print(f"Using regenerated GEN-SIM-DIGI-RAW file from scratch")
0129 print("=" * 40)
0130
0131 # Directory where all test configurations will be stored
0132 output_dir = "hlt_test_configs"
0133 # If the directory exists, remove it first
0134 if os.path.exists(output_dir):
0135 shutil.rmtree(output_dir)
0136
0137 # Create the directory
0138 os.makedirs(output_dir)
0139
0140 # Define the cmsDriver.py command to create the base configuration
0141 # If cachedInput is provided, use it as the input for the base cmsDriver command
0142 if args.cachedInput:
0143 base_cmsdriver_command = (
0144 f"cmsDriver.py Phase2 -s L1P2GT,HLT:75e33_timing "
0145 f"--conditions {global_tag} -n {num_events} --eventcontent FEVTDEBUGHLT "
0146 f"--geometry {geometry} --era {era} --filein {args.cachedInput} --fileout {output_dir}/hlt.root --no_exec "
0147 f"--mc --nThreads {num_threads} "
0148 f"--processName=HLTX "
0149 f"--inputCommands='keep *, drop *_hlt*_*_HLT, drop triggerTriggerFilterObjectWithRefs_l1t*_*_HLT' "
0150 f"--customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 "
0151 f'--customise_commands "process.options.wantSummary=True"'
0152 )
0153 else:
0154 base_cmsdriver_command = (
0155 f"cmsDriver.py Phase2 -s L1P2GT,HLT:75e33_timing "
0156 f"--conditions {global_tag} -n {num_events} --eventcontent FEVTDEBUGHLT "
0157 f"--geometry {geometry} --era {era} --filein file:{output_dir}/step1.root --fileout {output_dir}/hlt.root --no_exec "
0158 f"--nThreads {num_threads} "
0159 f"--customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 "
0160 f'--customise_commands "process.options.wantSummary=True"'
0161 )
0162
0163 # Add procModifiers if provided
0164 if proc_modifiers:
0165 base_cmsdriver_command += f" --procModifiers {proc_modifiers}"
0166
0167 # The base configuration file and the dumped configuration file
0168 base_config_file = os.path.join(output_dir, "Phase2_L1P2GT_HLT.py")
0169 dumped_config_file = os.path.join(output_dir, "Phase2_dump.py")
0170 log_file = os.path.join(output_dir, "hlt.log")
0171
0172 # Step 1: Run the cmsDriver.py command to generate the base configuration in the output directory
0173 print(f"Running cmsDriver.py to generate the base config: {base_config_file}")
0174 subprocess.run(base_cmsdriver_command, shell=True, cwd=output_dir)
0175
0176 # Step 2: Use edmConfigDump to dump the full configuration
0177 print(f"Dumping the full configuration using edmConfigDump to {dumped_config_file}")
0178 with open(dumped_config_file, "w") as dump_file, open(log_file, "w") as log:
0179 subprocess.run(f"edmConfigDump {base_config_file}", shell=True, stdout=dump_file, stderr=log)
0180
0181 # Step 3: Extract the list of HLT paths from the dumped configuration
0182 print(f"Extracting HLT paths from {dumped_config_file}...")
0183
0184 # Read the dumped configuration to extract HLT paths
0185 with open(dumped_config_file, "r") as f:
0186 config_content = f.read()
0187
0188 # Use regex to find all HLT and L1T paths defined in process.schedule
0189 unsorted_hlt_paths = re.findall(r"process\.(HLT_[A-Za-z0-9_]+|L1T_[A-Za-z0-9_]+)", config_content)
0190
0191 # Remove duplicates and sort alphabetically
0192 hlt_paths = sorted(set(unsorted_hlt_paths))
0193
0194 if not hlt_paths:
0195 print("No HLT paths found in the schedule!")
0196 exit(1)
0197
0198 print(f"Found {len(hlt_paths)} HLT paths.")
0199
0200 # Step 3b: Restrict paths using wildcard patterns if the option is provided
0201 if restrict_paths_to:
0202 valid_paths = set() # Using a set to store matched paths
0203
0204 # Iterate over each provided pattern
0205 for pattern in restrict_paths_to:
0206 # Use fnmatch to match the pattern to hlt_paths
0207 matched = fnmatch.filter(hlt_paths, pattern)
0208 valid_paths.update(matched) # Add matches to the set of valid paths
0209
0210 # If no matches found, emit a warning for that pattern
0211 if not matched:
0212 print(f"Warning: No paths matched the pattern: {pattern}")
0213
0214 # Convert the set to a sorted list
0215 valid_paths = sorted(valid_paths)
0216
0217 # If no valid paths remain after filtering, exit
0218 if not valid_paths:
0219 print("Error: None of the specified patterns matched any paths. Exiting.")
0220 exit(1)
0221
0222 # Update hlt_paths to contain only the valid ones
0223 hlt_paths = valid_paths
0224
0225 # Continue using the restricted hlt_paths further down the script
0226 print(f"Using {len(hlt_paths)} HLT paths after applying restrictions.")
0227
0228 # Step 4: Broadened Regex for Matching process.schedule
0229 schedule_match = re.search(
0230 r"(process\.schedule\s*=\s*cms\.Schedule\(\*?\s*\[)([\s\S]+?)(\]\s*\))",
0231 config_content
0232 )
0233
0234 if not schedule_match:
0235 print("No schedule match found after tweaking regex! Exiting...")
0236 exit(1)
0237 else:
0238 print(f"Matched schedule section.")
0239
0240 # Step 5: Generate N configurations by modifying the dumped config to keep only one path at a time
0241 for path_name in hlt_paths:
0242 # Create a new configuration file for this path
0243 config_filename = os.path.join(output_dir, f"Phase2_{path_name}.py")
0244
0245 # Define regex to find all HLT paths in the cms.Schedule and replace them
0246 def replace_hlt_paths(match):
0247 all_paths = match.group(2).split(", ")
0248 # Keep non-HLT/L1T paths and include only the current HLT or L1T path
0249 filtered_paths = [path for path in all_paths if not re.match(r"process\.(HLT_|L1T_)", path) or f"process.{path_name}" in path]
0250 return match.group(1) + ", ".join(filtered_paths) + match.group(3)
0251
0252 # Apply the regex to remove all HLT and L1T paths except the current one
0253 modified_content = re.sub(
0254 r"(process\.schedule\s*=\s*cms\.Schedule\(\*?\s*\[)([\s\S]+?)(\]\s*\))",
0255 replace_hlt_paths,
0256 config_content
0257 )
0258
0259 # Modify the fileout parameter to save a unique root file for each path
0260 modified_content = re.sub(
0261 r"fileName = cms\.untracked\.string\('.*'\)",
0262 f"fileName = cms.untracked.string('{output_dir}/{path_name}.root')",
0263 modified_content
0264 )
0265
0266 # Write the new config to a file
0267 with open(config_filename, "w") as new_config:
0268 new_config.write(modified_content)
0269
0270 print(f"Generated config: {config_filename}")
0271
0272 print(f"Generated {len(hlt_paths)} configuration files in the {output_dir} directory.")
0273
0274 # Step 6: Run cmsDriver.py for TTbar GEN,SIM steps and save the output in output_dir
0275 ttbar_config_file = os.path.join(output_dir, "TTbar_GEN_SIM_step.py")
0276 ttbar_command = (
0277 f"cmsDriver.py TTbar_14TeV_TuneCP5_cfi -s GEN,SIM,DIGI:pdigi_valid,L1TrackTrigger,L1,L1P2GT,DIGI2RAW -n {num_events} "
0278 f"--conditions {global_tag} --beamspot DBrealisticHLLHC --datatier GEN-SIM-DIGI-RAW "
0279 f"--eventcontent FEVTDEBUG --geometry {geometry} --era {era} "
0280 f"--relval 9000,100 --fileout {output_dir}/step1.root --nThreads {num_threads} "
0281 f"--customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 "
0282 f"--python_filename {ttbar_config_file}"
0283 )
0284
0285 if not args.cachedInput:
0286 print("Running TTbar GEN,SIM step...")
0287 run_command(ttbar_command, log_file=os.path.join(output_dir, "ttbar_gen_sim.log"))
0288
0289 # Directory containing HLT test configurations
0290 hlt_configs_dir = output_dir
0291
0292 # Check if the directory exists
0293 if not os.path.exists(hlt_configs_dir):
0294 print(f"Directory {hlt_configs_dir} not found! Exiting...")
0295 exit(1)
0296
0297 # Step 7: Function to run cmsRun on a given HLT config file and save the output
0298 def run_cmsrun(config_file):
0299 # Extract the HLT path name from the config file (e.g., "Phase2_HLT_IsoMu24_FromL1TkMuon.py")
0300 base_name = os.path.basename(config_file).replace("Phase2_", "").replace(".py", "")
0301 log_file = os.path.join(output_dir, f"{base_name}.log")
0302
0303 # Run the cmsRun command and log the output
0304 cmsrun_command = f"cmsRun {config_file}"
0305 result_code = run_command(cmsrun_command, log_file=log_file)
0306
0307 if result_code != 0:
0308 print(f"cmsRun failed for {config_file} with exit code {result_code}. Check {log_file} for details.")
0309 return result_code # Return the error code
0310
0311 print(f"cmsRun completed for {config_file}")
0312 return 0 # Return 0 for success
0313
0314 # Step 8: Loop through all files in hlt_test_configs and run cmsRun on each in parallel
0315 config_files = [
0316 f for f in os.listdir(hlt_configs_dir)
0317 if f.endswith(".py") and f.startswith("Phase2_") and f != "Phase2_dump.py"
0318 ]
0319 print(f"Found {len(config_files)} configuration files in {hlt_configs_dir}.")
0320
0321 # Run cmsRun on all config files in parallel and handle errors
0322 error_occurred = False
0323 with ThreadPoolExecutor(max_workers=num_parallel_jobs) as executor:
0324 futures = {executor.submit(run_cmsrun, os.path.join(output_dir, config_file)): config_file for config_file in config_files}
0325
0326 for future in as_completed(futures):
0327 config_file = futures[future]
0328 try:
0329 result_code = future.result()
0330 if result_code != 0:
0331 error_occurred = True
0332 print(f"cmsRun for {config_file} exited with code {result_code}")
0333 except Exception as exc:
0334 error_occurred = True
0335 print(f"cmsRun for {config_file} generated an exception: {exc}")
0336
0337 if error_occurred:
0338 print("-" * 40)
0339 print("One or more cmsRun jobs failed. Exiting with failure.")
0340 print("-" * 40)
0341 exit(1)
0342
0343 print("All cmsRun jobs submitted.")
0344
0345 # Step 9: Compare all HLT root files using hltDiff
0346 def compare_hlt_results(input_dir, num_events, max_workers=4):
0347 # List all root files starting with "HLT_" or "L1T_" in the output directory
0348 root_files = [f for f in os.listdir(input_dir) if f.endswith(".root") and (f.startswith("HLT_") or f.startswith("L1T_"))]
0349
0350 # Base file (hltrun output) to compare against
0351 base_root_file = os.path.join(input_dir, "hlt.root")
0352
0353 # Check if base_root_file exists
0354 if not os.path.exists(base_root_file):
0355 print(f"Base root file {base_root_file} not found! Exiting...")
0356 exit(1)
0357
0358 # Use ThreadPoolExecutor to run comparisons in parallel
0359 with ThreadPoolExecutor(max_workers=max_workers) as executor:
0360 futures = []
0361 for root_file in root_files:
0362 futures.append(executor.submit(compare_single_file, root_file, base_root_file, num_events, input_dir))
0363
0364 # Collect results as they complete
0365 for future in as_completed(futures):
0366 result = future.result()
0367 if result: # If there is an error message, print it and exit
0368 print("-" * 40)
0369 print(result)
0370 print("-" * 40)
0371 exit(1)
0372
0373 print("All HLT comparisons passed with no differences.")
0374
0375 # Step 10: Once all cmsRun jobs are completed, perform the hltDiff comparisons
0376 print("Performing HLT result comparisons...")
0377 compare_hlt_results(output_dir,num_events,num_parallel_jobs) # Adjust max_workers based on your CPU cores
0378
0379 # Step 11: Capture the end time and print the end timestamp
0380 end_time = time.time()
0381 end_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
0382 print("------------------------------------------------------------")
0383 print(f"Script ended at {end_timestamp}")
0384 print("------------------------------------------------------------")
0385
0386 # Step 12: Calculate the total execution time and print it
0387 total_time = end_time - start_time
0388 formatted_total_time = time.strftime("%H:%M:%S", time.gmtime(total_time))
0389 print("------------------------------------------------------------")
0390 print(f"Total execution time: {formatted_total_time}")
0391 print("------------------------------------------------------------")
0392
0393 print("All steps completed successfully.")