Back to home page

Project CMSSW displayed by LXR

 
 

    


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.")