Skip to content

Commit b55c3d6

Browse files
committed
introduced a python script for Phase-2 HLT integration tests
1 parent 393dfc8 commit b55c3d6

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import argparse
2+
import os
3+
import re
4+
import shutil
5+
import subprocess
6+
import sys
7+
import time
8+
from datetime import datetime
9+
from concurrent.futures import ThreadPoolExecutor, as_completed
10+
import Configuration.Geometry.defaultPhase2ConditionsEra_cff as _settings
11+
_PH2_GLOBAL_TAG, _PH2_ERA = _settings.get_era_and_conditions(_settings.DEFAULT_VERSION)
12+
13+
# Automatically generate the default geometry from DEFAULT_VERSION
14+
_PH2_GEOMETRY = f"Extended{_settings.DEFAULT_VERSION}"
15+
16+
# Get the actual era name from the version key
17+
_PH2_ERA_NAME = _settings.properties[2026][_settings.DEFAULT_VERSION]['Era']
18+
19+
# Function to display help information
20+
def print_help():
21+
help_text = """
22+
This script runs HLT test configurations for the CMS Phase2 upgrade.
23+
24+
Arguments:
25+
--globaltag : GlobalTag for the CMS conditions (required)
26+
--geometry : Geometry setting for the CMS process (required)
27+
--events : Number of events to process (default: 1)
28+
--threads : Number of threads to use (default: 1)
29+
30+
Example usage:
31+
python script.py --globaltag auto:phase2_realistic_T33 --geometry Extended2026D110 --events 10 --threads 4
32+
"""
33+
print(help_text)
34+
35+
# Function to run a shell command and handle errors
36+
def run_command(command, log_file=None, workdir=None):
37+
try:
38+
print(f"Running command: {command}")
39+
with open(log_file, "w") as log:
40+
subprocess.run(command, shell=True, check=True, cwd=workdir, stdout=log, stderr=log)
41+
except subprocess.CalledProcessError as e:
42+
print(f"Error running command: {e}")
43+
return e.returncode # Return the error code
44+
45+
return 0 # Return 0 for success
46+
47+
# Argument Parser for command-line configuration
48+
parser = argparse.ArgumentParser(description="Run HLT Test Configurations")
49+
parser.add_argument("--globaltag", default=_PH2_GLOBAL_TAG, help="GlobalTag for the CMS conditions")
50+
parser.add_argument("--geometry", default=_PH2_GEOMETRY, help="Geometry setting for the CMS process") # Auto-generated geometry default
51+
parser.add_argument("--era", default=_PH2_ERA_NAME, help="Era setting for the CMS process") # Convert _PH2_ERA to string
52+
parser.add_argument("--events", type=int, default=10, help="Number of events to process")
53+
parser.add_argument("--parallelJobs", type=int, default=4, help="Number of parallel cmsRun HLT jobs")
54+
parser.add_argument("--threads", type=int, default=1, help="Number of threads to use")
55+
56+
# Step 0: Capture the start time and print the start timestamp
57+
start_time = time.time()
58+
start_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
59+
print("------------------------------------------------------------")
60+
print(f"Script started at {start_timestamp}")
61+
print("------------------------------------------------------------")
62+
63+
# Parse arguments
64+
try:
65+
args = parser.parse_args()
66+
except SystemExit:
67+
print_help()
68+
sys.exit(0)
69+
70+
global_tag = args.globaltag
71+
era = args.era
72+
geometry = args.geometry
73+
num_events = args.events
74+
num_threads = args.threads
75+
num_parallel_jobs = args.parallelJobs
76+
77+
# Print the values in a nice formatted manner
78+
print(f"{'Configuration Summary':^40}")
79+
print("=" * 40)
80+
print(f"Global Tag: {global_tag}")
81+
print(f"Geometry: {geometry}")
82+
print(f"Era: {era}")
83+
print(f"Num Events: {num_events}")
84+
print(f"Num Threads: {num_threads}")
85+
print(f"Num Par. Jobs: {num_parallel_jobs}")
86+
print("=" * 40)
87+
88+
# Directory where all test configurations will be stored
89+
output_dir = "hlt_test_configs"
90+
# If the directory exists, remove it first
91+
if os.path.exists(output_dir):
92+
shutil.rmtree(output_dir)
93+
94+
# Create the directory
95+
os.makedirs(output_dir)
96+
97+
# Define the cmsDriver.py command to create the base configuration
98+
base_cmsdriver_command = (
99+
f"cmsDriver.py Phase2 -s L1P2GT,HLT:75e33_timing "
100+
f"--conditions {global_tag} -n {num_events} --eventcontent FEVTDEBUGHLT "
101+
f"--geometry {geometry} --era {era} --filein file:{output_dir}/step1.root --fileout {output_dir}/hlt.root --no_exec "
102+
f"--nThreads {num_threads} "
103+
f"--customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 "
104+
f'--customise_commands "process.options.wantSummary=True"'
105+
)
106+
107+
# The base configuration file and the dumped configuration file
108+
base_config_file = os.path.join(output_dir, "Phase2_L1P2GT_HLT.py")
109+
dumped_config_file = os.path.join(output_dir, "Phase2_dump.py")
110+
log_file = os.path.join(output_dir, "hlt.log")
111+
112+
# Step 1: Run the cmsDriver.py command to generate the base configuration in the output directory
113+
print(f"Running cmsDriver.py to generate the base config: {base_config_file}")
114+
subprocess.run(base_cmsdriver_command, shell=True, cwd=output_dir)
115+
116+
# Step 2: Use edmConfigDump to dump the full configuration
117+
print(f"Dumping the full configuration using edmConfigDump to {dumped_config_file}")
118+
with open(dumped_config_file, "w") as dump_file, open(log_file, "w") as log:
119+
subprocess.run(f"edmConfigDump {base_config_file}", shell=True, stdout=dump_file, stderr=log)
120+
121+
# Step 3: Extract the list of HLT paths from the dumped configuration
122+
print(f"Extracting HLT paths from {dumped_config_file}...")
123+
124+
# Read the dumped configuration to extract HLT paths
125+
with open(dumped_config_file, "r") as f:
126+
config_content = f.read()
127+
128+
# Use regex to find all HLT and L1T paths defined in process.schedule
129+
unsorted_hlt_paths = re.findall(r"process\.(HLT_[A-Za-z0-9_]+|L1T_[A-Za-z0-9_]+)", config_content)
130+
131+
# Remove duplicates and sort alphabetically
132+
hlt_paths = sorted(set(unsorted_hlt_paths))
133+
134+
if not hlt_paths:
135+
print("No HLT paths found in the schedule!")
136+
exit(1)
137+
138+
print(f"Found {len(hlt_paths)} HLT paths.")
139+
140+
# Step 4: Broadened Regex for Matching process.schedule
141+
schedule_match = re.search(
142+
r"(process\.schedule\s*=\s*cms\.Schedule\(\*?\s*\[)([\s\S]+?)(\]\s*\))",
143+
config_content
144+
)
145+
146+
if not schedule_match:
147+
print("No schedule match found after tweaking regex! Exiting...")
148+
exit(1)
149+
else:
150+
print(f"Matched schedule section.")
151+
152+
# Step 5: Generate N configurations by modifying the dumped config to keep only one path at a time
153+
for path_name in hlt_paths:
154+
# Create a new configuration file for this path
155+
config_filename = os.path.join(output_dir, f"Phase2_{path_name}.py")
156+
157+
# Define regex to find all HLT paths in the cms.Schedule and replace them
158+
def replace_hlt_paths(match):
159+
all_paths = match.group(2).split(", ")
160+
# Keep non-HLT/L1T paths and include only the current HLT or L1T path
161+
filtered_paths = [path for path in all_paths if not re.match(r"process\.(HLT_|L1T_)", path) or f"process.{path_name}" in path]
162+
return match.group(1) + ", ".join(filtered_paths) + match.group(3)
163+
164+
# Apply the regex to remove all HLT and L1T paths except the current one
165+
modified_content = re.sub(
166+
r"(process\.schedule\s*=\s*cms\.Schedule\(\*?\s*\[)([\s\S]+?)(\]\s*\))",
167+
replace_hlt_paths,
168+
config_content
169+
)
170+
171+
# Modify the fileout parameter to save a unique root file for each path
172+
modified_content = re.sub(
173+
r"fileName = cms\.untracked\.string\('.*'\)",
174+
f"fileName = cms.untracked.string('{output_dir}/{path_name}.root')",
175+
modified_content
176+
)
177+
178+
# Write the new config to a file
179+
with open(config_filename, "w") as new_config:
180+
new_config.write(modified_content)
181+
182+
print(f"Generated config: {config_filename}")
183+
184+
print(f"Generated {len(hlt_paths)} configuration files in the {output_dir} directory.")
185+
186+
# Step 6: Run cmsDriver.py for TTbar GEN,SIM steps and save the output in output_dir
187+
ttbar_config_file = os.path.join(output_dir, "TTbar_GEN_SIM_step.py")
188+
ttbar_command = (
189+
f"cmsDriver.py TTbar_14TeV_TuneCP5_cfi -s GEN,SIM,DIGI:pdigi_valid,L1TrackTrigger,L1,L1P2GT,DIGI2RAW -n {num_events} "
190+
f"--conditions {global_tag} --beamspot DBrealisticHLLHC --datatier GEN-SIM-DIGI-RAW "
191+
f"--eventcontent FEVTDEBUG --geometry {geometry} --era {era} "
192+
f"--relval 9000,100 --fileout {output_dir}/step1.root --nThreads {num_threads} "
193+
f"--customise SLHCUpgradeSimulations/Configuration/aging.customise_aging_1000 "
194+
f"--python_filename {ttbar_config_file}"
195+
)
196+
197+
print("Running TTbar GEN,SIM step...")
198+
run_command(ttbar_command, log_file=os.path.join(output_dir, "ttbar_gen_sim.log"))
199+
200+
# Directory containing HLT test configurations
201+
hlt_configs_dir = output_dir
202+
203+
# Check if the directory exists
204+
if not os.path.exists(hlt_configs_dir):
205+
print(f"Directory {hlt_configs_dir} not found! Exiting...")
206+
exit(1)
207+
208+
# Step 7: Function to run cmsRun on a given HLT config file and save the output
209+
def run_cmsrun(config_file):
210+
# Extract the HLT path name from the config file (e.g., "Phase2_HLT_IsoMu24_FromL1TkMuon.py")
211+
base_name = os.path.basename(config_file).replace("Phase2_", "").replace(".py", "")
212+
log_file = os.path.join(output_dir, f"{base_name}.log")
213+
214+
# Run the cmsRun command and log the output
215+
cmsrun_command = f"cmsRun {config_file}"
216+
result_code = run_command(cmsrun_command, log_file=log_file)
217+
218+
if result_code != 0:
219+
print(f"cmsRun failed for {config_file} with exit code {result_code}. Check {log_file} for details.")
220+
return result_code # Return the error code
221+
222+
print(f"cmsRun completed for {config_file}")
223+
return 0 # Return 0 for success
224+
225+
# Step 8: Loop through all files in hlt_test_configs and run cmsRun on each in parallel
226+
config_files = [f for f in os.listdir(hlt_configs_dir) if f.endswith(".py") and f.startswith("Phase2_")]
227+
print(f"Found {len(config_files)} configuration files in {hlt_configs_dir}.")
228+
229+
# Run cmsRun on all config files in parallel and handle errors
230+
error_occurred = False
231+
with ThreadPoolExecutor(max_workers=num_parallel_jobs) as executor:
232+
futures = {executor.submit(run_cmsrun, os.path.join(output_dir, config_file)): config_file for config_file in config_files}
233+
234+
for future in as_completed(futures):
235+
config_file = futures[future]
236+
try:
237+
result_code = future.result()
238+
if result_code != 0:
239+
error_occurred = True
240+
print(f"cmsRun for {config_file} exited with code {result_code}")
241+
except Exception as exc:
242+
error_occurred = True
243+
print(f"cmsRun for {config_file} generated an exception: {exc}")
244+
245+
if error_occurred:
246+
print("One or more cmsRun jobs failed. Exiting with failure.")
247+
exit(1)
248+
249+
print("All cmsRun jobs submitted.")
250+
251+
# Step 9: Compare all HLT root files using hltDiff
252+
def compare_hlt_results():
253+
# List all root files starting with "HLT_" in the output directory
254+
root_files = [f for f in os.listdir(output_dir) if f.endswith(".root") and (f.startswith("HLT_") or f.startswith("L1T_"))]
255+
256+
# Base file (hltrun output) to compare against
257+
base_root_file = os.path.join(output_dir, "hlt.root")
258+
259+
# Check if base_root_file exists
260+
if not os.path.exists(base_root_file):
261+
print(f"Base root file {base_root_file} not found! Exiting...")
262+
exit(1)
263+
264+
# Compare each root file with the base using hltDiff
265+
for root_file in root_files:
266+
root_path = os.path.join(output_dir, root_file)
267+
print(f"Comparing {root_path} with {base_root_file} using hltDiff...")
268+
269+
# Run the hltDiff command
270+
hlt_diff_command = f"hltDiff -o {base_root_file} -n {root_path}"
271+
result = subprocess.run(hlt_diff_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
272+
273+
# Decode and process the output
274+
output = result.stdout.decode("utf-8")
275+
print(output) # Print for debug purposes
276+
277+
# Use a dynamic check based on the number of events configured
278+
expected_match_string = f"Found {num_events} matching events, out of which 0 have different HLT results"
279+
280+
# Check if the output contains the expected match string
281+
if expected_match_string not in output:
282+
print(f"Error: {root_file} has different HLT results!")
283+
exit(1) # Exit with failure if differences are found
284+
285+
print("All HLT comparisons passed with no differences.")
286+
287+
# Step 10: Once all cmsRun jobs are completed, perform the hltDiff comparisons
288+
print("Performing HLT result comparisons...")
289+
compare_hlt_results()
290+
291+
# Step 11: Capture the end time and print the end timestamp
292+
end_time = time.time()
293+
end_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
294+
print("------------------------------------------------------------")
295+
print(f"Script ended at {end_timestamp}")
296+
print("------------------------------------------------------------")
297+
298+
# Step 12: Calculate the total execution time and print it
299+
total_time = end_time - start_time
300+
formatted_total_time = time.strftime("%H:%M:%S", time.gmtime(total_time))
301+
print("------------------------------------------------------------")
302+
print(f"Total execution time: {formatted_total_time}")
303+
print("------------------------------------------------------------")
304+
305+
print("All steps completed successfully.")

0 commit comments

Comments
 (0)