ATLAS Offline Software
Loading...
Searching...
No Matches
GridTools.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7
8//
9// includes
10//
11
13
20#include <TSystem.h>
21#include <chrono>
22#include <fstream>
23#include <mutex>
24#include <stdexcept>
25
26namespace sh = RCU::Shell;
27
28//
29// method implementations
30//
31
32namespace SH
33{
34 ANA_MSG_SOURCE (msgGridTools, "SampleHandler_GridTools")
35 using namespace msgGridTools;
36
37 namespace
38 {
39 struct ProxyData
40 {
41 // the clock we use
42 using clock = std::chrono::steady_clock;
43
44 // don't really need a mutex as the code unlikely to be
45 // multi-threaded, but may just as well put one to protect the
46 // global/static variable
47 std::recursive_mutex mutex;
48
49 // whether we have confirmed that we do have a proxy
50 bool haveProxy = false;
51
52 // the expiration time of the proxy (if we have one)
53 clock::time_point proxyExpiration;
54
55 bool checkVomsProxy ()
56 {
57 std::lock_guard<std::recursive_mutex> lock (mutex);
58
59 if (haveProxy == false)
60 {
61 ANA_MSG_INFO ("checking for valid grid proxy");
62 int rc = 0;
63 std::string output =
64 RCU::Shell::exec_read ("voms-proxy-info --actimeleft", rc);
65 if (rc != 0)
66 {
67 ANA_MSG_INFO ("no valid proxy found");
68 } else
69 {
70 std::istringstream str (output);
71 unsigned seconds = 0;
72
73 if (!(str >> seconds))
74 {
75 // Output format is more complicated if RPM isn't installed
76 std::istringstream str2 (output.substr(output.rfind('\n',output.size()-2)+1,std::string::npos));
77
78 if (!(str2 >> seconds)){
79 ANA_MSG_INFO ("failed to parse command output: " << output);
80 } else
81 {
82 proxyExpiration = clock::now() + std::chrono::seconds (seconds);
83 haveProxy = true;
84 } // Second try was successful
85
86 } else
87 {
88 proxyExpiration = clock::now() + std::chrono::seconds (seconds);
89 haveProxy = true;
90 } // First try was successful
91 }
92 }
93
94 return haveProxy &&
95 proxyExpiration > clock::now() + std::chrono::minutes (20);
96 }
97
98 void ensureVomsProxy (unsigned tries = 0)
99 {
100 std::lock_guard<std::recursive_mutex> lock (mutex);
101
102 if (checkVomsProxy())
103 return;
104
105 // rationale: cap the number of retries so that we do not loop
106 // forever if voms-proxy-init keeps succeeding but the
107 // resulting proxy stays too short-lived or unparseable.
108 if (tries >= 3)
109 throw std::runtime_error ("failed to obtain a valid grid proxy after several attempts");
110
111 if (haveProxy)
112 {
113 ANA_MSG_INFO ("proxy expired or about to expire");
114 } else
115 {
116 ANA_MSG_INFO ("no proxy found");
117 }
118 ANA_MSG_INFO ("trying to set up a new proxy");
119 haveProxy = false;
120 RCU::Shell::exec ("voms-proxy-init -voms atlas");
121 ensureVomsProxy (tries + 1);
122 }
123 };
124
125 ProxyData& proxyData ()
126 {
127 // Methods of ProxyData() are thread-safe.
128 static ProxyData result ATLAS_THREAD_SAFE;
129 return result;
130 }
131
132
133
136 std::vector<std::string>
137 readLineList (const std::string& text,
138 const std::string& begin)
139 {
140 std::vector<std::string> result;
141
142 for (std::string::size_type split = 0;
143 (split = text.find (begin, split)) != std::string::npos;
144 ++ split)
145 {
146 if (split == 0 || text[split-1] == '\n')
147 {
148 split += begin.size();
149 auto split2 = text.find ("\n", split);
150 if (split2 == std::string::npos)
151 split2 = text.size();
152 std::string subresult = text.substr (split, split2 - split);
153 // rationale: strip surrounding whitespace in O(n). guard
154 // against an empty/all-whitespace value (front()/back() on
155 // an empty string is UB) and use find_first/last_not_of
156 // rather than isspace on a possibly-negative char.
157 const char *const whitespace = " \t\n\r\f\v";
158 const auto first = subresult.find_first_not_of (whitespace);
159 if (first == std::string::npos)
160 subresult.clear ();
161 else
162 {
163 const auto last = subresult.find_last_not_of (whitespace);
164 subresult = subresult.substr (first, last - first + 1);
165 }
166 result.push_back (std::move (subresult));
167 }
168 }
169 return result;
170 }
171
172
173
176 std::string readLine (const std::string& text,
177 const std::string& begin)
178 {
179 auto lines = readLineList (text, begin);
180 if (lines.empty())
181 throw std::runtime_error ("failed to find line starting with: " + begin);
182 if (lines.size() > 1)
183 throw std::runtime_error ("multiple lines starting with: " + begin);
184 return lines.at(0);
185 }
186
187
188
191 unsigned readLineUnsigned (const std::string& text,
192 const std::string& begin)
193 {
194 const auto line = readLine (text, begin);
195 std::istringstream str (line);
196 unsigned result = 0;
197 if (!(str >> result) || !str.eof())
198 throw std::runtime_error ("failed to convert " + line + " into an unsigned");
199 return result;
200 }
201
202
203
205 std::string rucioSetupCommand ()
206 {
207 return "source $ATLAS_LOCAL_ROOT_BASE/user/atlasLocalSetup.sh -q && lsetup --force 'rucio -w'";
208 }
209 }
210
211
212
213 const std::string& downloadStageEnvVar ()
214 {
215 static const std::string result = "SAMPLEHANDLER_RUCIO_DOWNLOAD";
216 return result;
217 }
218
219
220
222 {
223 return proxyData().checkVomsProxy();
224 }
225
226
227
229 {
230 proxyData().ensureVomsProxy();
231 }
232
233
234
235 std::vector<std::string>
236 faxListFilesGlob (const std::string& name, const std::string& filter)
237 {
238#pragma GCC diagnostic push
239#pragma GCC diagnostic ignored "-Wpragmas"
240#pragma GCC diagnostic ignored "-Wunknown-pragmas"
241#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
242 return faxListFilesRegex (name, RCU::glob_to_regexp (filter));
243#pragma GCC diagnostic pop
244 }
245
246
247
248 std::vector<std::string>
249 faxListFilesRegex (const std::string& name, const std::string& filter)
250 {
251 RCU_REQUIRE_SOFT (!name.empty());
252 RCU_REQUIRE_SOFT (name.find('*') == std::string::npos);
253 RCU_REQUIRE_SOFT (!filter.empty());
254
256
257 static const std::string separator = "------- SampleHandler Split -------";
258 std::vector<std::string> result;
259
260 ANA_MSG_INFO ("querying FAX for dataset " << name);
261 std::string output = sh::exec_read ("source $ATLAS_LOCAL_ROOT_BASE/user/atlasLocalSetup.sh -q && lsetup --force fax && echo " + separator + " && fax-get-gLFNs " + sh::quote (name));
262 auto split = output.rfind (separator + "\n");
263 if (split == std::string::npos)
264 throw std::runtime_error ("couldn't find separator in: " + output);
265
266 std::istringstream str (output.substr (split + separator.size() + 1));
267 std::regex pattern (filter);
268 std::string line;
269 while (std::getline (str, line))
270 {
271 if (!line.empty())
272 {
273 if (!line.starts_with ("root:"))
274 throw std::runtime_error ("faxListFilesRegex: couldn't parse line: " + line);
275
276 std::string::size_type split1 = line.rfind (":");
277 std::string::size_type split2 = line.rfind ("/");
278 if (split1 < split2)
279 split1 = split2;
280 if (split1 != std::string::npos)
281 {
282 if (RCU::match_expr (pattern, line.substr (split1+1)))
283 result.push_back (line);
284 } else
285 throw std::runtime_error ("faxListFilesRegex: couldn't parse line: " + line);
286 }
287 }
288 if (result.size() == 0)
289 ANA_MSG_WARNING ("dataset " << name << " did not contain any files. this is likely not right");
290 return result;
291 }
292
293
294
295 std::vector<std::string>
296 rucioDirectAccessGlob (const std::string& name, const std::string& filter,
297 const std::string& selectOptions)
298 {
299 return rucioDirectAccessRegex (name, RCU::glob_to_regexp (filter),
300 selectOptions);
301 }
302
303
304
305 std::vector<std::string>
306 rucioDirectAccessRegex (const std::string& name, const std::string& filter,
307 const std::string& selectOptions)
308 {
309 RCU_REQUIRE_SOFT (!name.empty());
310 RCU_REQUIRE_SOFT (name.find('*') == std::string::npos);
311 RCU_REQUIRE_SOFT (!filter.empty());
312
314
315 static const std::string separator = "------- SampleHandler Split -------";
316
317 ANA_MSG_INFO ("querying rucio for dataset " << name);
318 std::string output = sh::exec_read (rucioSetupCommand() + " && echo " + separator + " && rucio list-file-replicas --pfns --protocols root " + selectOptions + " " + sh::quote (name));
319 auto split = output.rfind (separator + "\n");
320 if (split == std::string::npos)
321 throw std::runtime_error ("couldn't find separator in: " + output);
322 std::istringstream str (output.substr (split + separator.size() + 1));
323
324 // this is used to avoid getting two copies of the same file. we
325 // first fill them in a map by filename, then copy them into a
326 // vector
327 std::map<std::string,std::string> resultMap;
328
329 std::regex urlPattern ("^root://.*");
330 std::regex pattern (filter);
331 std::string line;
332 while (std::getline (str, line))
333 {
334 if (line.empty())
335 {
336 // no-op
337 } else if (!RCU::match_expr (urlPattern, line))
338 {
339 ANA_MSG_INFO ("couldn't handle line: " << line);
340 } else
341 {
342 std::string::size_type split = line.rfind ("/");
343 if (split != std::string::npos)
344 {
345 std::string filename = line.substr (split+1);
346 if (RCU::match_expr (pattern, filename))
347 resultMap[filename] = line;
348 } else
349 throw std::runtime_error ("rucioDirectAccessRegex: couldn't parse line: " + line);
350 }
351 }
352
353 std::vector<std::string> result;
354 for (const auto& file : resultMap)
355 result.push_back (file.second);
356 if (result.size() == 0)
357 ANA_MSG_WARNING ("dataset " + name + " did not contain any files. this is likely not right");
358 return result;
359 }
360
361
362
363 std::vector<RucioListDidsEntry> rucioListDids (const std::string& dataset)
364 {
365 RCU_REQUIRE_SOFT (!dataset.empty());
366
368
369 static const std::string separator = "------- SampleHandler Split -------";
370 std::vector<RucioListDidsEntry> result;
371
372 ANA_MSG_INFO ("querying rucio for dataset " << dataset);
373 std::string output = sh::exec_read (rucioSetupCommand() + " && echo " + separator + " && rucio list-dids " + sh::quote (dataset));
374 auto split = output.rfind (separator + "\n");
375 if (split == std::string::npos)
376 throw std::runtime_error ("couldn't find separator in: " + output);
377
378 std::istringstream str (output.substr (split + separator.size() + 1));
379 std::regex pattern ("^\\| ([a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+) +\\| ([a-zA-Z0-9_.-]+) +\\| *$");
380 std::string line;
381 while (std::getline (str, line))
382 {
383 std::smatch what;
384 if (std::regex_match (line, what, pattern))
385 {
386 RucioListDidsEntry entry;
387 entry.scope = what[1];
388 entry.name = what[2];
389 entry.type = what[3];
390 result.push_back (entry);
391 }
392 }
393 return result;
394 }
395
396
397
398 std::vector<RucioListFileReplicasEntry>
399 rucioListFileReplicas (const std::string& dataset)
400 {
401 RCU_REQUIRE_SOFT (!dataset.empty());
402
404
405 static const std::string separator = "------- SampleHandler Split -------";
406 std::vector<RucioListFileReplicasEntry> result;
407
408 std::string command = rucioSetupCommand() + " && echo " + separator + " && rucio list-file-replicas --protocols root " + sh::quote (dataset);
409
410 ANA_MSG_INFO ("querying rucio for dataset " << dataset);
411 std::string output = sh::exec_read ( command );
412 auto split = output.rfind (separator + "\n");
413 if (split == std::string::npos)
414 throw std::runtime_error ("couldn't find separator in: " + output);
415
416 std::istringstream str (output.substr (split + separator.size() + 1));
417 std::regex pattern ("^\\| +([^ ]+) +\\| +([^ ]+) +\\| +([^ ]+ [^ ]+) +\\| +([^ ]+) +\\| +([^: ]+): ([^ ]+) +\\| *$");
418 std::string line;
419 while (std::getline (str, line))
420 {
421 std::smatch what;
422 if (std::regex_match (line, what, pattern) &&
423 what[1] != "SCOPE")
424 {
426 entry.scope = what[1];
427 entry.name = what[2];
428 entry.filesize = what[3];
429 entry.adler32 = what[4];
430 entry.disk = what[5];
431 entry.replica = what[6];
432 result.push_back (entry);
433 }
434 }
435 return result;
436 }
437
438
439
440 std::map<std::string,std::unique_ptr<MetaObject> >
441 rucioGetMetadata (const std::set<std::string>& datasets)
442 {
443 RCU_REQUIRE_SOFT (!datasets.empty());
444
446
447 static const std::string separator = "------- SampleHandler Split -------";
448 std::map<std::string,std::unique_ptr<MetaObject> > result;
449
450 std::string command = rucioSetupCommand() + " && echo " + separator + " && rucio get-metadata";
451 for (auto& dataset : datasets)
452 {
453 RCU_REQUIRE_SOFT (!dataset.empty());
454 command += " " + sh::quote (dataset);
455 }
456
457 ANA_MSG_INFO ("querying rucio for meta-data");
458 std::string output = sh::exec_read (command);
459 auto split = output.rfind (separator + "\n");
460 if (split == std::string::npos)
461 throw std::runtime_error ("couldn't find separator in: " + output);
462
463 std::istringstream str (output.substr (split + separator.size() + 1));
464 std::regex pattern ("^([^:]+): *(.+)$");
465 std::string line;
466 auto meta = std::make_unique<MetaObject>();
467
468 auto addMeta = [&] ()
469 {
470 std::string name = meta->castString ("scope") + ":" + meta->castString ("name");
471 if (result.find (name) != result.end())
472 throw std::runtime_error ("rucioGetMetadata: read " + name + " twice");
473 result[name] = std::move (meta);
474 };
475
476 while (std::getline (str, line))
477 {
478 std::smatch what;
479 if (line == "------")
480 {
481 addMeta ();
482 meta = std::make_unique<MetaObject>();
483 } else if (std::regex_match (line, what, pattern))
484 {
485 if (meta->get (what[1]))
486 throw std::runtime_error (std::string("duplicate entry: ") + what[1].str());
487 meta->setString (what[1], what[2]);
488 } else if (!line.empty())
489 {
490 ANA_MSG_WARNING ("couldn't parse line: " << line);
491 }
492 }
493 addMeta ();
494
495 for (auto& subresult : result)
496 {
497 if (datasets.find (subresult.first) == datasets.end())
498 throw std::runtime_error ("received result for dataset not requested: " + subresult.first);
499 }
500 for (auto& dataset : datasets)
501 {
502 if (result.find (dataset) == result.end())
503 throw std::runtime_error ("received no result for dataset: " + dataset);
504 }
505
506 return result;
507 }
508
509
510
511 RucioDownloadResult rucioDownload (const std::string& location,
512 const std::string& dataset)
513 {
515
516 const std::string separator = "------- SampleHandler Split -------";
517 std::string command = rucioSetupCommand() + " && echo " + separator + " && cd " + sh::quote (location) + " && rucio download " + sh::quote (dataset) + " 2>&1";
518
519 ANA_MSG_INFO ("starting rucio download " + dataset + " into " + location);
520 std::string output = sh::exec_read (command);
521 auto split = output.rfind (separator + "\n");
522 if (split == std::string::npos)
523 throw std::runtime_error ("couldn't find separator in: " + output);
524 output = output.substr (split + separator.size() + 1);
525
526 RucioDownloadResult result;
527 result.did = readLine (output, "DID ");
528 result.totalFiles = readLineUnsigned (output, "Total files (DID): ");
529 result.downloadedFiles = readLineUnsigned (output, "Downloaded files: ");
530 result.alreadyLocal = readLineUnsigned (output, "Files already found locally: ");
531 result.notDownloaded = readLineUnsigned (output, "Files that cannot be downloaded: ");
532 return result;
533 }
534
535
536
537 std::vector<RucioDownloadResult>
538 rucioDownloadList (const std::string& location,
539 const std::vector<std::string>& datasets)
540 {
541 std::vector<RucioDownloadResult> result;
542 for (auto& dataset : datasets)
543 result.push_back (rucioDownload (location, dataset));
544 return result;
545 }
546
547
548
549 std::vector<std::string>
550 rucioCacheDatasetGlob (const std::string& location,
551 const std::string& dataset,
552 const std::string& fileGlob)
553 {
554 std::vector<std::string> result;
555
556 std::string path = location;
557 if (path.empty() || path.back() != '/')
558 path += "/";
559 if (dataset.find (':') != std::string::npos)
560 path += dataset.substr (dataset.find (':')+1);
561 else
562 path += dataset;
563 const std::string finished {
564 path + "-finished"};
565
566 // check if the finished file does not exist
567 // note that AccessPathName has the weirdest calling convention
568 //
569 // rationale: this check-then-download is not safe against two jobs
570 // caching the same dataset into the same directory concurrently
571 // (they can both see the marker missing and download at the same
572 // time); guarding that properly would need an exclusive lock on
573 // the directory. we do at least check that the marker file was
574 // created, so an unwritable directory fails loudly instead of
575 // silently re-downloading on every call.
576 if (gSystem->AccessPathName (finished.c_str()) != 0)
577 {
578 RucioDownloadResult status = rucioDownload (location, dataset);
579 if (status.downloadedFiles + status.alreadyLocal < status.totalFiles)
580 throw std::runtime_error ("failed to download all files of " + dataset);
581 // this just creates an empty file
582 std::ofstream finishedFile (finished.c_str());
583 if (!finishedFile)
584 throw std::runtime_error ("failed to create marker file: " + finished);
585 }
586
587 std::string output = sh::exec_read ("find " + sh::quote (path) + " -type f -name " + sh::quote (fileGlob));
588 std::istringstream str (output);
589 std::string line;
590 while (std::getline (str, line))
591 {
592 if (!line.empty())
593 result.push_back (line);
594 }
595 return result;
596 }
597}
#define RCU_REQUIRE_SOFT(x)
Definition Assert.h:141
macros for messaging and checking status codes
#define ANA_MSG_INFO(xmsg,...)
Macro printing info messages.
#define ANA_MSG_WARNING(xmsg,...)
Macro printing warning messages.
#define ANA_MSG_SOURCE(NAME, TITLE)
the source code part of ANA_MSG_SOURCE
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
static Double_t rc
Define macros for attributes used to control the static checker.
#define ATLAS_THREAD_SAFE
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179
bool first
Definition DeMoScan.py:534
std::string exec_read(const std::string &cmd)
effects: execute the given command and return the output returns: the output of the command guarantee...
Definition ShellExec.cxx:35
void exec(const std::string &cmd)
effects: execute the given command guarantee: strong failures: out of memory II failures: system fail...
Definition ShellExec.cxx:27
std::string quote(const std::string &name)
effects: quote the given name to protect it from the shell returns: the quoted name guarantee: strong...
Definition ShellExec.cxx:65
bool match_expr(const std::regex &expr, std::string_view str)
returns: whether we can match the entire string with the regular expression guarantee: strong failure...
std::string glob_to_regexp(std::string_view glob)
returns: a string that is the regular expression equivalent of the given glob expression guarantee: s...
This module provides a lot of global definitions, forward declarations and includes that are used by ...
Definition DiskList.cxx:21
std::vector< RucioDownloadResult > rucioDownloadList(const std::string &location, const std::vector< std::string > &datasets)
run rucio-download with multiple datasets
RucioDownloadResult rucioDownload(const std::string &location, const std::string &dataset)
run rucio-download
const std::string & downloadStageEnvVar()
the name of the environment variable containing the directory for staging files from the grid
std::vector< std::string > faxListFilesGlob(const std::string &name, const std::string &filter)
list the FAX URLs for all the files in the dataset or dataset container matching the given filter (as...
std::vector< RucioListFileReplicasEntry > rucioListFileReplicas(const std::string &dataset)
run rucio-list-file-replicas for the given dataset
std::vector< RucioListDidsEntry > rucioListDids(const std::string &dataset)
run rucio-list-dids for the given dataset
void ensureVomsProxy()
ensure that we have a valid VOMS proxy available
std::map< std::string, std::unique_ptr< MetaObject > > rucioGetMetadata(const std::set< std::string > &datasets)
run rucio-get-metadata for the given list of datasets
std::vector< std::string > rucioDirectAccessRegex(const std::string &name, const std::string &filter, const std::string &selectOptions)
list the rucio URLs for all the files in the dataset or dataset container matching the given filter (...
std::vector< std::string > rucioCacheDatasetGlob(const std::string &location, const std::string &dataset, const std::string &fileGlob)
download the dataset, and return a list matching the pattern
bool checkVomsProxy()
return whether we have a valid VOMS proxy available
std::vector< std::string > faxListFilesRegex(const std::string &name, const std::string &filter)
list the FAX URLs for all the files in the dataset or dataset container matching the given filter (as...
std::vector< std::string > rucioDirectAccessGlob(const std::string &name, const std::string &filter, const std::string &selectOptions)
list the rucio URLs for all the files in the dataset or dataset container matching the given filter (...
output
Definition merge.py:16
-diff
the result from rucio_download
Definition GridTools.h:175
one entry from the rucio-list-dids command
Definition GridTools.h:107
one entry from the rucio-list-file-replicas command
Definition GridTools.h:131
TFile * file