Project
Loading...
Searching...
No Matches
CCDBFetcherHelper.cxx
Go to the documentation of this file.
1// Copyright 2019-2025 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11#include "CCDBFetcherHelper.h"
12#include "CCDBHelpers.h"
14#include "Framework/Signpost.h"
17#include <TError.h>
18#include <TMemFile.h>
19
21
22namespace o2::framework
23{
24
26{
27 // find the first = sign in the string. If present drop everything after it
28 // and between it and the previous /.
29 auto pos = path.find('=');
30 if (pos == std::string::npos) {
31 auto entry = remappings.find(path);
32 return apis[entry == remappings.end() ? "" : entry->second];
33 }
34 auto pos2 = path.rfind('/', pos);
35 if (pos2 == std::string::npos || pos2 == pos - 1 || pos2 == 0) {
36 throw runtime_error_f("Malformed path %s", path.c_str());
37 }
38 auto entry = remappings.find(path.substr(0, pos2));
39 return apis[entry == remappings.end() ? "" : entry->second];
40}
41
42namespace
43{
44bool isOnlineRun(DataTakingContext const& dtc)
45{
47}
48} // namespace
49
51{
52 auto defHost = options.get<std::string>("condition-backend");
53 auto checkRate = options.get<int>("condition-tf-per-query");
54 auto checkMult = options.get<int>("condition-tf-per-query-multiplier");
55 helper.useTFSlice = options.get<int>("condition-use-slice-for-prescaling");
56 helper.timeToleranceMS = options.get<int64_t>("condition-time-tolerance");
57 helper.queryPeriodGlo = checkRate > 0 ? checkRate : std::numeric_limits<int>::max();
58 helper.queryPeriodFactor = checkMult == 0 ? 1 : checkMult;
59 std::string extraCond{};
60 if (helper.useTFSlice) {
61 extraCond = ". Use TFSlice";
62 if (helper.useTFSlice > 0) {
63 extraCond += fmt::format(" + max TFcounter jump <= {}", helper.useTFSlice);
64 }
65 }
66 LOGP(info, "CCDB Backend at: {}, validity check for every {} TF{}{}", defHost, helper.queryPeriodGlo,
67 helper.queryPeriodFactor == 1 ? std::string{} : (helper.queryPeriodFactor > 0 ? fmt::format(", (query for high-rate objects downscaled by {})", helper.queryPeriodFactor) : fmt::format(", (query downscaled as TFcounter%{})", -helper.queryPeriodFactor)),
68 extraCond);
69 LOGP(info, "Hook to enable signposts for CCDB messages at {}", (void*)&private_o2_log_ccdb->stacktrace);
70 auto remapString = options.get<std::string>("condition-remap");
71 ParserResult result = parseRemappings(remapString.c_str());
72 if (!result.error.empty()) {
73 throw runtime_error_f("Error while parsing remapping string %s", result.error.c_str());
74 }
75 helper.remappings = result.remappings;
76 helper.apis[""].init(defHost); // default backend
77 LOGP(info, "Initialised default CCDB host {}", defHost);
78 //
79 for (auto& entry : helper.remappings) { // init api instances for every host seen in the remapping
80 if (helper.apis.find(entry.second) == helper.apis.end()) {
81 helper.apis[entry.second].init(entry.second);
82 LOGP(info, "Initialised custom CCDB host {}", entry.second);
83 }
84 LOGP(info, "{} is remapped to {}", entry.first, entry.second);
85 }
86 helper.createdNotBefore = std::to_string(options.get<int64_t>("condition-not-before"));
87 helper.createdNotAfter = std::to_string(options.get<int64_t>("condition-not-after"));
88}
89
91{
92 std::unordered_map<std::string, std::string> remappings;
93 std::string currentUrl = "";
94
95 enum ParsingStates {
96 IN_BEGIN,
97 IN_BEGIN_URL,
98 IN_BEGIN_TARGET,
99 IN_END_TARGET,
100 IN_END_URL
101 };
102 ParsingStates state = IN_BEGIN;
103
104 while (true) {
105 switch (state) {
106 case IN_BEGIN: {
107 if (*str == 0) {
108 return {remappings, ""};
109 }
110 state = IN_BEGIN_URL;
111 }
112 case IN_BEGIN_URL: {
113 if ((strncmp("http://", str, 7) != 0) && (strncmp("https://", str, 8) != 0 && (strncmp("file://", str, 7) != 0))) {
114 return {remappings, "URL should start with either http:// or https:// or file://"};
115 }
116 state = IN_END_URL;
117 } break;
118 case IN_END_URL: {
119 char const* c = strchr(str, '=');
120 if (c == nullptr) {
121 return {remappings, "Expecting at least one target path, missing `='?"};
122 }
123 if ((c - str) == 0) {
124 return {remappings, "Empty url"};
125 }
126 currentUrl = std::string_view(str, c - str);
127 state = IN_BEGIN_TARGET;
128 str = c + 1;
129 } break;
130 case IN_BEGIN_TARGET: {
131 if (*str == 0) {
132 return {remappings, "Empty target"};
133 }
134 state = IN_END_TARGET;
135 } break;
136 case IN_END_TARGET: {
137 char const* c = strpbrk(str, ",;");
138 if (c == nullptr) {
139 if (remappings.count(str)) {
140 return {remappings, fmt::format("Path {} requested more than once.", str)};
141 }
142 remappings[std::string(str)] = currentUrl;
143 return {remappings, ""};
144 }
145 if ((c - str) == 0) {
146 return {remappings, "Empty target"};
147 }
148 auto key = std::string(str, c - str);
149 if (remappings.count(str)) {
150 return {remappings, fmt::format("Path {} requested more than once.", key)};
151 }
152 remappings[key] = currentUrl;
153 if (*c == ';') {
154 state = IN_BEGIN_URL;
155 } else {
156 state = IN_BEGIN_TARGET;
157 }
158 str = c + 1;
159 } break;
160 }
161 }
162}
163
164auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr<CCDBFetcherHelper> const& helper,
165 std::vector<CCDBFetcherHelper::FetchOp> const& ops,
166 TimingInfo& timingInfo,
168 DataAllocator& allocator) -> std::vector<CCDBFetcherHelper::Response>
169{
170 int objCnt = -1;
171 // We use the timeslice, so that we hook into the same interval as the rest of the
172 // callback.
173 static bool isOnline = isOnlineRun(dtc);
174
175 auto sid = _o2_signpost_id_t{(int64_t)timingInfo.timeslice};
176 O2_SIGNPOST_START(ccdb, sid, "populateCacheWith", "Starting to populate cache with CCDB objects");
177 std::vector<Response> responses;
178 for (auto& op : ops) {
179 int64_t timestampToUse = op.timestamp;
180 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Fetching object for route %{public}s", DataSpecUtils::describe(op.spec).data());
181 objCnt++;
182 auto concrete = DataSpecUtils::asConcreteDataMatcher(op.spec);
183 Output output{concrete.origin, concrete.description, concrete.subSpec};
184 auto&& v = allocator.makeVector<char>(output);
185 std::map<std::string, std::string> metadata;
186 std::map<std::string, std::string> headers;
187 std::string path = op.url;
188 std::string etag = "";
189 int chRate = helper->queryPeriodGlo;
190 bool checkValidity = false;
191 if (op.runDependent > 0) {
192 if (op.runDependent == 1) {
193 metadata["runNumber"] = std::format("{}", op.runNumber);
194 } else if (op.runDependent == 2) {
195 timestampToUse = op.runNumber;
196 } else {
197 LOGP(fatal, "Undefined ccdb-run-dependent option {} for spec {}/{}/{}", op.runDependent,
198 concrete.origin.as<std::string>(), concrete.description.as<std::string>(), int(concrete.subSpec));
199 }
200 }
201 for (auto m : op.metadata) {
202 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Adding metadata %{public}s: %{public}s to the request", m.key.data(), m.value.data());
203 metadata[m.key] = m.value;
204 }
205 if (op.queryRate != 0) {
206 chRate = op.queryRate * helper->queryPeriodFactor;
207 }
208
209 const auto url2uuid = helper->mapURL2UUID.find(path);
210 if (url2uuid != helper->mapURL2UUID.end()) {
211 etag = url2uuid->second.etag;
212 // We check validity every chRate timeslices or if the cache is expired
213 uint64_t validUntil = url2uuid->second.cacheValidUntil;
214 // When the cache was populated. If the cache was populated after the timestamp, we need to check validity.
215 uint64_t cachePopulatedAt = url2uuid->second.cachePopulatedAt;
216 // If timestamp is before the time the element was cached or after the claimed validity, we need to check validity, again
217 // when online.
218 bool cacheExpired = (validUntil <= timestampToUse) || (op.timestamp < cachePopulatedAt);
219 if (isOnline || cacheExpired) {
220 if (!helper->useTFSlice) {
221 checkValidity = chRate > 0 ? (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) : (timingInfo.tfCounter % -chRate) == 0;
222 } else {
223 checkValidity = chRate > 0 ? (std::abs(int(timingInfo.timeslice - url2uuid->second.lastCheckedSlice)) >= chRate) : (timingInfo.timeslice % -chRate) == 0;
224 if (!checkValidity && helper->useTFSlice > std::abs(chRate)) { // make sure the interval is tolerated unless the check rate itself is too large
225 checkValidity = std::abs(int(timingInfo.tfCounter) - url2uuid->second.lastCheckedTF) > helper->useTFSlice;
226 }
227 }
228 }
229 } else {
230 checkValidity = true; // never skip check if the cache is empty
231 }
232
233 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "checkValidity is %{public}s for tf%{public}s %zu of %{public}s", checkValidity ? "true" : "false", helper->useTFSlice ? "ID" : "Slice", helper->useTFSlice ? timingInfo.timeslice : timingInfo.tfCounter, path.data());
234
235 const auto& api = helper->getAPI(path);
236 if (checkValidity && (!api.isSnapshotMode() || etag.empty())) { // in the snapshot mode the object needs to be fetched only once
237 LOGP(detail, "Loading {} for timestamp {}", path, timestampToUse);
238 api.loadFileToMemory(v, path, metadata, timestampToUse, &headers, etag, helper->createdNotAfter, helper->createdNotBefore);
239 if ((headers.count("Error") != 0) || (etag.empty() && v.empty())) {
240 LOGP(fatal, "Unable to find CCDB object {}/{}", path, timestampToUse);
241 // FIXME: I should send a dummy message.
242 continue;
243 }
244 // printing in case we find a default entry
245 if (headers.find("default") != headers.end()) {
246 LOGP(detail, "******** Default entry used for {} ********", path);
247 }
248 helper->mapURL2UUID[path].lastCheckedTF = timingInfo.tfCounter;
249 helper->mapURL2UUID[path].lastCheckedSlice = timingInfo.timeslice;
250 if (etag.empty()) {
251 helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid
252 helper->mapURL2UUID[path].cachePopulatedAt = timestampToUse;
253 helper->mapURL2UUID[path].cacheMiss++;
254 helper->mapURL2UUID[path].size = v.size();
255 helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize);
256 helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize);
257 auto size = v.size();
258 helper->totalFetchedBytes += size;
259 helper->totalRequestedBytes += size;
260 api.appendFlatHeader(v, headers);
261 auto cacheId = CCDBHelpers::adoptAndReplaceCachedMessage(allocator, helper->mapURL2DPLCache, path, output, std::move(v), header::gSerializationMethodCCDB);
262 helper->mapURL2DPLCache[path] = cacheId;
263 responses.emplace_back(Response{.id = cacheId, .size = size, .request = nullptr});
264 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Caching %{public}s for %{public}s (DPL id %" PRIu64 ", size %zu)", path.data(), headers["ETag"].data(), cacheId.value, size);
265 continue;
266 }
267 if (v.size()) { // but should be overridden by fresh object
268 helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid
269 helper->mapURL2UUID[path].cachePopulatedAt = timestampToUse;
270 helper->mapURL2UUID[path].cacheValidUntil = headers["Cache-Valid-Until"].empty() ? 0 : std::stoul(headers["Cache-Valid-Until"]);
271 helper->mapURL2UUID[path].cacheMiss++;
272 helper->mapURL2UUID[path].size = v.size();
273 helper->mapURL2UUID[path].minSize = std::min(v.size(), helper->mapURL2UUID[path].minSize);
274 helper->mapURL2UUID[path].maxSize = std::max(v.size(), helper->mapURL2UUID[path].maxSize);
275 auto size = v.size();
276 helper->totalFetchedBytes += size;
277 helper->totalRequestedBytes += size;
278 api.appendFlatHeader(v, headers);
279 auto cacheId = CCDBHelpers::adoptAndReplaceCachedMessage(allocator, helper->mapURL2DPLCache, path, output, std::move(v), header::gSerializationMethodCCDB);
280 helper->mapURL2DPLCache[path] = cacheId;
281 responses.emplace_back(Response{.id = cacheId, .size = size, .request = nullptr});
282 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Caching %{public}s for %{public}s (DPL id %" PRIu64 ")", path.data(), headers["ETag"].data(), cacheId.value);
283 continue;
284 } else {
285 // Only once the etag is actually used, we get the information on how long the object is valid
286 helper->mapURL2UUID[path].cacheValidUntil = headers["Cache-Valid-Until"].empty() ? 0 : std::stoul(headers["Cache-Valid-Until"]);
287 }
288 }
289 // cached object is fine
290 auto cacheId = helper->mapURL2DPLCache[path];
291 O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Reusing %{public}s for %{public}s (DPL id %" PRIu64 ")", path.data(), headers["ETag"].data(), cacheId.value);
292 helper->mapURL2UUID[path].cacheHit++;
293 responses.emplace_back(Response{.id = cacheId, .size = helper->mapURL2UUID[path].size, .request = nullptr});
294 allocator.adoptFromCache(output, cacheId, header::gSerializationMethodCCDB);
295 // the outputBuffer was not used, can we destroy it?
296 }
297 O2_SIGNPOST_END(ccdb, sid, "populateCacheWith", "Finished populating cache with CCDB objects");
298 return responses;
299};
300
301} // namespace o2::framework
benchmark::State & state
size_t cachePopulatedAt
std::string etag
uint32_t op
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
uint16_t pos
Definition RawData.h:3
uint32_t c
Definition RawData.h:2
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_END(log, id, name, format,...)
Definition Signpost.h:609
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
Definition Signpost.h:523
#define O2_SIGNPOST_START(log, id, name, format,...)
Definition Signpost.h:603
StringRef key
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
const GLdouble * v
Definition glcorearb.h:832
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
bool isOnlineRun(DataTakingContext const &dtc)
RuntimeErrorRef runtime_error_f(const char *,...)
constexpr o2::header::SerializationMethod gSerializationMethodCCDB
Definition DataHeader.h:329
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
o2::ccdb::CcdbApi & getAPI(const std::string &path)
std::unordered_map< std::string, std::string > remappings
static auto populateCacheWith(std::shared_ptr< CCDBFetcherHelper > const &helper, std::vector< FetchOp > const &ops, TimingInfo &timingInfo, DataTakingContext &dtc, DataAllocator &allocator) -> std::vector< Response >
std::unordered_map< std::string, o2::ccdb::CcdbApi > apis
static void initialiseHelper(CCDBFetcherHelper &helper, ConfigParamRegistry const &options)
static ParserResult parseRemappings(char const *)
static DataAllocator::CacheId adoptAndReplaceCachedMessage(DataAllocator &allocator, std::unordered_map< std::string, DataAllocator::CacheId > const &cache, std::string const &path, Output const &output, o2::pmr::vector< char > &&v, o2::header::SerializationMethod method)
static std::string describe(InputSpec const &spec)
static ConcreteDataMatcher asConcreteDataMatcher(InputSpec const &input)
DeploymentMode deploymentMode
Where we thing this is running.
header::DataOrigin origin
Definition Output.h:28
const std::string str