Skip to content

Commit c03ece6

Browse files
authored
Merge pull request #50118 from AdrianoDee/das_n_ev_improvements_feb26
Improvements for `das-up-to-nevents` Script
2 parents 581c1e2 + ce951c7 commit c03ece6

3 files changed

Lines changed: 110 additions & 79 deletions

File tree

Configuration/PyReleaseValidation/python/relval_standard.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,8 @@ def addFixedEventsTestingWfs(years, pds, eras):
638638
addFixedEventsTestingWfs(['2023'], pds, eras)
639639

640640
## 2022
641-
pds = ['ZeroBias', 'JetHT', 'Tau', 'BTagMu']
642-
eras = ['B','C','D','E']
641+
pds = ['JetHT', 'Tau', 'BTagMu']
642+
eras = ['C','D','E']
643643
addFixedEventsTestingWfs(['2022'], pds, eras)
644644

645645
######################################################################################################################################

Configuration/PyReleaseValidation/scripts/das-up-to-nevents.py

Lines changed: 107 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python3
22
import pycurl
33
from io import BytesIO
4-
import pycurl
54
import ast
65
import subprocess
76
import pandas as pd
@@ -12,83 +11,92 @@
1211
import json
1312
import sys
1413
import itertools
15-
import json
1614
import re
15+
import logging
16+
17+
# Configure logging
18+
logging.basicConfig(
19+
level=logging.INFO,
20+
format='[%(asctime)s - %(levelname)s] : %(message)s',
21+
handlers=[
22+
logging.FileHandler('das-up-to-nevents.log')
23+
]
24+
)
25+
logger = logging.getLogger(__name__)
26+
27+
# Constants to skip short runs and the beginning of each run
28+
MIN_RUN_LENGTH = 40
29+
SKIP_LUMIS = 20
1730

1831
## Helpers
1932
base_cert_url = "https://cms-service-dqmdc.web.cern.ch/CAF/certification/"
2033
base_cert_eos = "/eos/user/c/cmsdqm/www/CAF/certification/"
2134
base_cert_cvmfs = "/cvmfs/cms-griddata.cern.ch/cat/metadata/DC/"
2235

2336
def get_url_clean(url):
24-
37+
logger.debug(f"Fetching URL: {url}")
2538
buffer = BytesIO()
2639
c = pycurl.Curl()
2740
c.setopt(c.URL, url)
2841
c.setopt(c.WRITEDATA, buffer)
2942
c.perform()
3043
c.close()
31-
3244
return BeautifulSoup(buffer.getvalue(), "lxml").text
3345

3446
def get_lumi_ranges(i):
47+
logger.debug("Calculating luminosity ranges")
3548
result = []
3649
for _, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
3750
b = list(b)
38-
result.append([b[0][1],b[-1][1]])
51+
result.append([b[0][1], b[-1][1]])
3952
return result
4053

41-
def das_do_command(cmd):
42-
out = subprocess.check_output(cmd, shell=True, executable="/bin/bash").decode('utf8')
43-
return out.split("\n")
44-
4554
def das_key(dataset):
55+
logger.debug(f"Creating DAS key for dataset: {dataset}")
4656
return 'dataset='+dataset if "#" not in dataset else 'block='+dataset
4757

48-
def das_file_site(dataset, site):
49-
cmd = "dasgoclient --query='file %s site=%s'"%(das_key(dataset),site)
50-
out = das_do_command(cmd)
51-
df = pd.DataFrame(out,columns=["file"])
58+
def das_query(query):
59+
cmd = f"dasgoclient"
60+
# For cms-bot deterministic caching see cms-sw#50101
61+
if "JENKINS_PREFIX" in os.environ:
62+
cmd = f"{cmd} --limit=100 -unique"
63+
cmd = f"{cmd} --query='{query}'"
64+
logger.debug(f"Executing DAS query: {cmd}")
65+
out = subprocess.check_output(cmd, shell=True, executable="/bin/bash").decode('utf8')
66+
result = out.split("\n")
67+
logger.debug(f"Query result: {result}")
68+
return result
5269

70+
def das_file_site(dataset, site):
71+
out = das_query(f"file {das_key(dataset)} site={site}")
72+
df = pd.DataFrame(out, columns=["file"])
5373
return df
5474

55-
def das_file_data(dataset,opt=""):
56-
cmd = "dasgoclient --query='file %s %s| grep file.name, file.nevents'"%(das_key(dataset),opt)
57-
out = das_do_command(cmd)
58-
out = [np.array(r.split(" "))[[0,3]] for r in out if len(r) > 0]
59-
60-
df = pd.DataFrame(out,columns=["file","events"])
75+
def das_file_data(dataset, opt=""):
76+
out = das_query(f"file {das_key(dataset)} {opt} | grep file.name, file.nevents")
77+
out = [r.split(" ") for r in out if len(r) > 0]
78+
out = [[r[0], r[3]] for r in out if len(r) > 3]
79+
df = pd.DataFrame(out, columns=["file", "events"])
6180
df.events = df.events.values.astype(int)
62-
6381
return df
6482

65-
def das_lumi_data(dataset,opt=""):
66-
67-
cmd = "dasgoclient --query='file,lumi,run %s %s'"%(das_key(dataset),opt)
68-
69-
out = das_do_command(cmd)
70-
out = [r.split(" ") for r in out if len(r)>0]
71-
72-
df = pd.DataFrame(out,columns=["file","run","lumis"])
73-
83+
def das_lumi_data(dataset, opt=""):
84+
out = das_query(f"file,lumi,run {das_key(dataset)} {opt}")
85+
out = [r.split(" ") for r in out if len(r) > 0]
86+
df = pd.DataFrame(out, columns=["file", "run", "lumis"])
7487
return df
7588

76-
def das_run_events_data(dataset,run,opt=""):
77-
cmd = "dasgoclient --query='file %s run=%s %s | sum(file.nevents) '"%(das_key(dataset),run,opt)
78-
out = das_do_command(cmd)[0]
79-
80-
out = [o for o in out.split(" ") if "sum" not in o]
81-
out = int([r.split(" ") for r in out if len(r)>0][0][0])
82-
83-
return out
84-
85-
def das_run_data(dataset,opt=""):
86-
cmd = "dasgoclient --query='run %s %s '"%(das_key(dataset),opt)
87-
out = das_do_command(cmd)
89+
def das_run_events_data(dataset, run, opt=""):
90+
out = das_query(f"file {das_key(dataset)} run={run} {opt} | sum(file.nevents)")
91+
out = out[0].split(" ")[-1] if out else "0"
92+
return int(out)
8893

94+
def das_run_data(dataset, opt=""):
95+
out = das_query(f"run {das_key(dataset)} {opt}")
8996
return out
9097

9198
def no_intersection():
99+
logger.error("No intersection between JSON and dataset. Exiting.")
92100
print("No intersection between:")
93101
print(" - json : ", best_json)
94102
print(" - dataset: ", dataset)
@@ -102,22 +110,28 @@ def no_intersection():
102110
parser.add_argument('--threshold','-t', help ="Event threshold per file",type=int,default=-1)
103111
parser.add_argument('--events','-e', help ="Tot number of events targeted",type=int,default=-1)
104112
parser.add_argument('--outfile','-o', help='Dump results to file', type=str, default=None)
105-
parser.add_argument('--pandas', '-pd',action='store_true',help="Store the whole dataset (no event or threshold cut) in a csv")
113+
parser.add_argument('--pandas', '-pd',action='store_true',help="Store the whole dataset (no event or threshold cut) in a csv")
106114
parser.add_argument('--proxy','-p', help='Allow to parse a x509 proxy if needed', type=str, default=None)
107115
parser.add_argument('--site','-s', help='Only data at specific site', type=str, default=None)
108116
parser.add_argument('--lumis','-l', help='Output file for lumi ranges for the selected files (if black no lumiranges calculated)', type=str, default=None)
109-
parser.add_argument('--precheck','-pc', action='store_true', help='Check run per run before building the dataframes, to avoid huge caching.')
110117
parser.add_argument('--nogolden','-ng', action='store_true', help='Do not crosscheck the dataset run and lumis with a Golden json for data certification')
111118
parser.add_argument('--run','-r', help ="Target a specific run",type=int,default=None,nargs="+")
119+
parser.add_argument('--debug', action='store_true', help='Enable debug logging')
112120
args = parser.parse_args()
113121

122+
in_bot = "JENKINS_PREFIX" in os.environ
123+
124+
logging.getLogger().setLevel(logging.DEBUG if args.debug or in_bot else logging.INFO)
125+
114126
if args.proxy is not None:
115127
os.environ["X509_USER_PROXY"] = args.proxy
128+
logger.debug(f"Set X509_USER_PROXY to {args.proxy}")
116129
elif "X509_USER_PROXY" not in os.environ:
130+
logger.error("No X509 proxy set. Exiting.")
117131
print("No X509 proxy set. Exiting.")
118132
sys.exit(1)
119-
120-
## Check if we are in the cms-bot "environment"
133+
134+
## Check if we are in the cms-bot "environment"
121135
dataset = args.dataset
122136
events = args.events
123137
threshold = args.threshold
@@ -126,23 +140,29 @@ def no_intersection():
126140
lumis = args.lumis
127141
runs = args.run
128142
das_opt = ""
129-
143+
144+
logger.info(f"Dataset: {dataset}, Events: {events}, Threshold: {threshold}, Outfile: {outfile}, Site: {site}, Lumis: {lumis}, Runs: {runs}")
145+
130146
if runs is not None:
131147
das_opt = "run in %s"%(str([int(r) for r in runs]))
148+
logger.debug(f"DAS options for runs: {das_opt}")
132149

133150
if not args.nogolden:
134-
151+
logger.debug("Checking for golden JSON files")
152+
135153
## get the greatest golden json
136154
year = dataset.split("Run")[1][2:4] # from 20XX to XX
137155
PD = dataset.split("/")[1]
138156
cert_type = "Collisions" + str(year)
139157
if "Cosmics" in dataset:
140158
cert_type = "Cosmics" + str(year)
141159
elif "Commisioning" in dataset:
142-
cert_type = "Commisioning2020"
160+
cert_type = "Commisioning2020"
143161
elif "HI" in PD:
144162
cert_type = "Collisions" + str(year) + "HI"
145-
163+
164+
logger.info(f"Certification type: {cert_type}")
165+
146166
cvmfs_path = base_cert_cvmfs + cert_type + "/"
147167
eos_path = ""
148168
web_path = ""
@@ -151,37 +171,44 @@ def no_intersection():
151171
if os.path.isdir(cvmfs_path):
152172
cvmfs_path = cvmfs_path + "/latest/"
153173
json_list_full = os.listdir(cvmfs_path)
174+
logger.info(f"Found JSON files in CVMFS: {json_list_full}")
154175
## ... if not we try eos ...
155176
if len(json_list_full)==0:
156177
eos_path = base_cert_eos + cert_type + "/"
157178
if os.path.isdir(eos_path):
158179
json_list_full = os.listdir(eos_path)
180+
logger.info(f"Found JSON files in EOS: {json_list_full}")
159181
## ... if not we go to the website
160182
if len(json_list_full)==0:
161183
web_path = base_cert_url + cert_type + "/"
162184
json_list_full = get_url_clean(web_path).split("\n")
185+
logger.info(f"Found JSON files on web: {json_list_full}")
186+
163187
pattern = re.compile("(cert_collisions\d{4}_\d*_\d*_golden.json)(\s|$)", re.IGNORECASE)
164188
json_list = [match.group(1) for entry in json_list_full for match in [re.search(pattern, entry)] if match and match.group(1)]
165189
if len(json_list)==0:
190+
logger.error(f"No matching JSON files found from {source} ({path}). The full list was:\n{list_full}")
166191
raise RuntimeError("No matching JSON files found from {source} ({path}). The full list was:\n{list_full}".format(
167192
source="web" if web_path else "eos" if eos_path else "cvmfs",
168193
path=web_path if web_path else eos_path if eos_path else cvmfs_path,
169194
list_full='\n'.join(json_list_full),
170195
))
171196

172-
# the larger the better, assuming file naming schema
197+
# the larger the better, assuming file naming schema
173198
# Cert_X_RunStart_RunFinish_Type.json
174199
# TODO if args.run keep golden only with right range
175200
run_ranges = [int(c.split("_")[-2]) - int(c.split("_")[-3]) for c in json_list]
176201
latest_json = np.array(json_list[np.argmax(run_ranges)]).reshape(1,-1)[0].astype(str)
177202
best_json = str(latest_json[0])
203+
logger.info(f"Selected JSON file: {best_json}")
204+
178205
if not web_path:
179206
with open((eos_path if eos_path else cvmfs_path) + "/" + best_json) as js:
180207
golden = json.load(js)
181208
else:
182209
golden = get_url_clean(web_path + best_json)
183210
golden = ast.literal_eval(golden) #converts string to dict
184-
211+
185212
# skim for runs in input
186213
if runs is not None:
187214
for k in golden:
@@ -193,7 +220,11 @@ def no_intersection():
193220
for k in golden:
194221
R = []
195222
for r in golden[k]:
196-
R = R + [f for f in range(r[0],r[1]+1)]
223+
# skipping short runs
224+
if r[1]-r[0] + 1 < MIN_RUN_LENGTH:
225+
continue
226+
227+
R = R + [f for f in range(r[0]+SKIP_LUMIS,r[1]+1)]
197228
golden_flat[k] = R
198229

199230
# let's just check there's an intersection between the
@@ -206,26 +237,20 @@ def no_intersection():
206237

207238
# building the dataframe, cleaning for bad lumis
208239
golden_data_runs_tocheck = golden_data_runs
209-
210-
if args.precheck:
211-
golden_data_runs_tocheck = []
212-
# Here we check run per run.
213-
# This implies more dasgoclient queries, but smaller outputs
214-
# useful when running the IB/PR tests not to have huge
215-
# query results that have to be cached.
216-
sum_events = 0
217-
for r in golden_data_runs:
218-
sum_events = sum_events + int(das_run_events_data(dataset,r))
219-
golden_data_runs_tocheck.append(r)
220-
if events > 0 and sum_events > events:
221-
break
222-
das_opt = "run in %s"%(str([int(g) for g in golden_data_runs_tocheck]))
223-
240+
241+
if in_bot:
242+
best_run = golden_flat
243+
best_run, max_value = max([(k,golden_flat[k]) for k in golden_data_runs_tocheck], key = lambda x: len(set(x[1])))
244+
das_opt = "run in %s"%([b for b in [best_run]])
245+
246+
logger.debug(f"DAS options for runs: {das_opt}")
247+
224248
df = das_lumi_data(dataset,opt=das_opt).merge(das_file_data(dataset,opt=das_opt),on="file",how="inner") # merge file informations with run and lumis
225249

226250
df["lumis"] = [[int(ff) for ff in f.replace("[","").replace("]","").split(",")] for f in df.lumis.values]
227-
251+
228252
if not args.nogolden:
253+
logger.debug("Filtering data based on golden JSON")
229254

230255
df_rs = []
231256
for r in golden_data_runs_tocheck:
@@ -239,6 +264,7 @@ def no_intersection():
239264
if df_r["events"].sum() < threshold:
240265
continue
241266

267+
# taking only fully certified files, i.e. files for which all lumis are in the golden json
242268
good_lumis = np.array([len([ll for ll in l if ll in golden_flat[r]]) for l in df_r.lumis])
243269
n_lumis = np.array([len(l) for l in df_r.lumis])
244270
df_rs.append(df_r[good_lumis==n_lumis])
@@ -250,33 +276,38 @@ def no_intersection():
250276

251277
df.loc[:,"min_lumi"] = [min(f) for f in df.lumis]
252278
df.loc[:,"max_lumi"] = [max(f) for f in df.lumis]
253-
df = df.sort_values(["run","min_lumi","max_lumi"])
254-
279+
df.loc[:,"n_lumis"] = df.loc[:,"max_lumi"] - df.loc[:,"min_lumi"] + 1
280+
281+
df = df.sort_values(["run","n_lumis","min_lumi","max_lumi"])
282+
255283
if site is not None:
256284
df = df.merge(das_file_site(dataset,site),on="file",how="inner")
257285

258286
if args.pandas:
259287
df.to_csv(dataset.replace("/","")+".csv")
260288

261-
if events > 0:
289+
if events > 0 and not in_bot:
290+
logger.debug(f"Filtering files with more than {events} events")
262291
df = df[df["events"] <= events] #jump too big files
263292
df.loc[:,"sum_evs"] = df.loc[:,"events"].cumsum()
264293
df = df[df["sum_evs"] < events]
265-
294+
266295
files = df.file
267-
296+
268297
if lumis is not None:
298+
logger.debug(f"Saving luminosity ranges to {lumis}")
269299
lumi_ranges = { int(r) : list(get_lumi_ranges(np.sort(np.concatenate(df.loc[df["run"]==r,"lumis"].values).ravel()).tolist())) for r in np.unique(df.run.values).tolist()}
270-
300+
271301
with open(lumis, 'w') as fp:
272302
json.dump(lumi_ranges, fp)
273303

274304
if outfile is not None:
305+
logger.debug(f"Saving file list to {outfile}")
275306
with open(outfile, 'w') as f:
276307
for line in files:
277-
f.write(f"{line}\n")
308+
f.write(f"{line}\n")
278309
else:
310+
logger.debug("Outputting file list to console")
279311
print("\n".join(files))
280312

281313
sys.exit(0)
282-

Configuration/PyReleaseValidation/scripts/runTheMatrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def runSelected(opt):
113113
139.001, # Run2021 MinimumBias Commissioning2021
114114

115115
# 2022
116-
2022.0030001, # Run2022C JetHT
116+
2022.0010001, # Run2022C JetHT
117117

118118
# 2023
119119
2023.0020001, # Run2023D JetMET0

0 commit comments

Comments
 (0)