Project
Loading...
Searching...
No Matches
DeviceMetricsHelper.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 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
15#include "Framework/Logger.h"
16#include <cassert>
17#include <cinttypes>
18#include <cstdlib>
19
20#include <algorithm>
21#include <string_view>
22#include <tuple>
23#include <iostream>
24#include <limits>
25#include <unordered_set>
26
27namespace o2::framework
28{
29
30// Parser state for the metric string
46// Parses a metric in the form
47//
48// [METRIC] <name>[/<begin>[-<end>]],<type> <value> <timestamp>[ <tag>,<tag>]
49//
50// FIXME: I should probably also support the following format:
51//
52// [METRIC] <name>/<index1>:<index2>:<index3>,<type> <value>,<value>,<value> <timestamp>[ <tag>,<tag>]
53//
54// I.e. allow to specify multiple values for different indices.
56{
58 char const* cur = s.data();
59 char const* next = s.data();
60 char* err = nullptr;
61 // We need to keep track of the last and last but one space
62 // to be able to parse the timestamp and tags.
63 char const* lastSpace = nullptr;
64 char const* previousLastSpace = nullptr;
65
66 while (true) {
67 auto previousState = state;
69 err = nullptr;
70 switch (previousState) {
72 if (strncmp(cur, "[METRIC] ", 9) == 0) {
73 next = cur + 8;
75 match.beginKey = cur + 9;
76 }
77 } break;
79 next = strpbrk(cur, ",/\n");
80 if (next == nullptr || *next == '\n' || ((cur == next) && (match.endKey - match.beginKey == 0))) {
81 } else if (*next == '/') {
82 // Notice that in case of indexed metrics
83 // we start by considering the key only
84 // the prefix, in case there is a range
85 // afterwards. If not, we will update the
86 // key to include the index.
87 match.endKey = next;
89 } else if (*next == ',') {
90 match.endKey = next;
92 }
93 } break;
95 match.firstIndex = strtol(cur, &err, 10);
96 next = err;
97 if (*next == '-') {
99 } else if (*next == ',') {
100 // In case there is no range, we
101 // maintain backwards compatibility
102 // with the old key format.
103 match.lastIndex = match.firstIndex + 1;
104 match.endKey = next;
106 } else {
107 // We are still in prefix, we simply
108 // it a / in the middle of the metric
109 // so we skip it and go back to prefix.
110 match.firstIndex = -1;
111 match.endKey = next + 1;
113 }
114 } break;
116 match.lastIndex = strtol(cur, &err, 10);
117 next = err;
118 if (cur == next) {
119 } else if (*next == ',') {
121 }
122 } break;
124 match.type = (MetricType)strtol(cur, &err, 10);
125 next = err;
126 if (cur == next) {
127 } else if (*next == ' ') {
128 if (match.type == MetricType::Int) {
130 } else if (match.type == MetricType::Uint64) {
132 } else if (match.type == MetricType::Float) {
134 } else if (match.type == MetricType::String) {
136 match.beginStringValue = next + 1;
137 match.endStringValue = next + 1;
138 } else {
139 break;
140 }
141 if (strncmp(match.beginKey, "data_relayer/", 13) == 0 && match.beginKey[13] != 'w' && match.beginKey[13] != 'h') {
142 match.type = MetricType::Enum;
143 }
144 }
145 } break;
147 match.intValue = strtol(cur, &err, 10);
148 next = err;
149 if (cur == next) {
150 } else if (*next == ' ') {
151 match.uint64Value = match.intValue;
152 match.floatValue = match.intValue;
154 }
155 } break;
157 match.uint64Value = strtoull(cur, &err, 10);
158 next = err;
159 if (cur == next) {
160 } else if (*next == ' ') {
161 match.intValue = match.uint64Value;
162 match.floatValue = match.uint64Value;
164 }
165 } break;
167 match.floatValue = strtof(cur, &err);
168 next = err;
169 if (cur == next) {
170 } else if (*next == ' ') {
171 match.uint64Value = match.floatValue;
172 match.intValue = match.floatValue;
174 }
175 } break;
177 next = (char*)memchr(cur, ' ', s.data() + s.size() - cur);
178 if (next == nullptr) {
179 auto timestamp = strtoull(cur, &err, 10);
180 if (*err != '\n' && *err != '\0') {
181 // last word is not a number, backtrack
182 // to the previous space and assume it is the
183 // timestamp
184 match.endStringValue = previousLastSpace;
185 next = previousLastSpace;
187 } else {
188 // We found a timestamp as last word.
189 match.endStringValue = lastSpace;
190 match.timestamp = timestamp;
191 next = err;
193 }
194 break;
195 // This is the end of the string
196 } else if (*next == ' ') {
197 previousLastSpace = lastSpace;
198 lastSpace = next;
200 break;
201 }
202 } break;
204 match.timestamp = strtoull(cur, &err, 10);
205 next = err;
206 if (cur == next) {
207 } else if (*next == ' ') {
209 } else if (next == s.data() + s.size() || *next == '\n') {
211 }
212 } break;
214 // Tags not handled for now.
216 } break;
218 return true;
220 return false;
221 }
222 }
223 cur = next + 1;
224 }
225}
226
227static auto updatePrefix = [](std::string_view prefix, DeviceMetricsInfo& info, bool hasPrefix, std::vector<MetricPrefixIndex>::iterator pi) -> void {
228 // Insert the prefix if needed
229 if (!hasPrefix) {
230 MetricPrefix metricPrefix;
231 metricPrefix.size = prefix.size();
232 memcpy(metricPrefix.prefix, prefix.data(), prefix.size());
233 metricPrefix.prefix[prefix.size()] = '\0';
234
235 MetricPrefixIndex metricPrefixIdx;
236 metricPrefixIdx.index = info.metricPrefixes.size();
237 info.metricPrefixes.push_back(metricPrefix);
238
239 auto sortedInsert = std::distance(info.metricLabelsPrefixesSortedIdx.begin(), pi);
240 info.metricLabelsPrefixesSortedIdx.insert(info.metricLabelsPrefixesSortedIdx.begin() + sortedInsert, metricPrefixIdx);
241
242 auto previousEnd = 0;
243 if (sortedInsert != 0) {
244 previousEnd = info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[sortedInsert - 1].index].end;
245 }
246 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[sortedInsert].index].begin = previousEnd;
247 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[sortedInsert].index].end = previousEnd + 1;
248 for (size_t i = sortedInsert + 1; i < info.metricLabelsPrefixesSortedIdx.size(); i++) {
249 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[i].index].begin++;
250 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[i].index].end++;
251 }
252 } else {
253 info.metricPrefixes[pi->index].end++;
254 auto insertLocation = std::distance(info.metricLabelsPrefixesSortedIdx.begin(), pi);
255 for (size_t i = insertLocation + 1; i < info.metricLabelsPrefixesSortedIdx.size(); i++) {
256 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[i].index].begin++;
257 info.metricPrefixes[info.metricLabelsPrefixesSortedIdx[i].index].end++;
258 }
259 }
260};
261
262static constexpr int DPL_MAX_METRICS_PER_DEVICE = 1024 * 128;
263
264static auto initMetric = [](DeviceMetricsInfo& info) -> void {
265 // Add the timestamp buffer for it
266 info.max.push_back(std::numeric_limits<float>::lowest());
267 info.min.push_back(std::numeric_limits<float>::max());
268 info.average.push_back(0);
269 info.maxDomain.push_back(std::numeric_limits<size_t>::lowest());
270 info.minDomain.push_back(std::numeric_limits<size_t>::max());
271 info.changed.push_back(false);
272 if (info.metricLabels.size() > DPL_MAX_METRICS_PER_DEVICE) {
273 for (size_t i = 0; i < info.metricLabels.size(); i++) {
274 std::cout << info.metricLabels[i].label << std::endl;
275 }
276 throw runtime_error_f("Too many metrics for a given device. Max is DPL_MAX_METRICS_PER_DEVICE=%d.", DPL_MAX_METRICS_PER_DEVICE);
277 }
278};
279
280auto storeIdx = [](DeviceMetricsInfo& info, MetricType type) -> size_t {
281 switch (type) {
282 case MetricType::Int:
283 return info.intMetrics.size();
285 return info.stringMetrics.size();
287 return info.floatMetrics.size();
289 return info.uint64Metrics.size();
290 case MetricType::Enum:
291 return info.enumMetrics.size();
292 default:
293 return -1;
294 }
295};
296
298 // Create a new metric
299 MetricInfo metricInfo{
300 .type = type,
301 .storeIdx = storeIdx(info, type),
302 .pos = 0,
303 .filledMetrics = 0,
304 };
305 // Add a new empty buffer for it of the correct kind
306 switch (type) {
307 case MetricType::Int:
308 info.intMetrics.emplace_back(MetricsStorage<int>{});
309 info.intTimestamps.emplace_back(TimestampsStorage<int>{});
310 break;
312 info.stringMetrics.emplace_back(MetricsStorage<StringMetric>{});
314 break;
316 info.floatMetrics.emplace_back(MetricsStorage<float>{});
317 info.floatTimestamps.emplace_back(TimestampsStorage<float>{});
318 break;
320 info.uint64Metrics.emplace_back(MetricsStorage<uint64_t>{});
322 break;
323 case MetricType::Enum:
324 info.enumMetrics.emplace_back(MetricsStorage<int8_t>{});
325 info.enumTimestamps.emplace_back(TimestampsStorage<int8_t>{});
326 break;
327 default:
328 throw std::runtime_error("Unknown metric type");
329 };
330 initMetric(info);
331 return metricInfo;
332};
333
335{
336 // Find the prefix for the metric
337 auto key = std::string_view(name, strlen(name));
338
339 auto slash = key.find_first_of("/");
340 if (slash == std::string_view::npos) {
341 slash = 1;
342 }
343 auto prefix = std::string_view(key.data(), slash);
344
345 // Find the prefix.
346 auto cmpPrefixFn = [&prefixes = info.metricPrefixes](MetricPrefixIndex const& a, std::string_view b) -> bool {
347 return std::string_view(prefixes[a.index].prefix, prefixes[a.index].size) < b;
348 };
349
350 auto pi = std::lower_bound(info.metricLabelsPrefixesSortedIdx.begin(),
352 prefix,
353 cmpPrefixFn);
354 bool hasPrefix = pi != info.metricLabelsPrefixesSortedIdx.end() && (std::string_view(info.metricPrefixes[pi->index].prefix, info.metricPrefixes[pi->index].size) == prefix);
355
356 auto rb = info.metricLabelsAlphabeticallySortedIdx.begin() + (hasPrefix ? info.metricPrefixes[pi->index].begin : 0);
357 auto re = info.metricLabelsAlphabeticallySortedIdx.begin() + (hasPrefix ? info.metricPrefixes[pi->index].end : info.metricLabelsAlphabeticallySortedIdx.size());
358
359 // Find the metric based on the label. Create it if not found.
360 auto cmpFn = [&labels = info.metricLabels, offset = hasPrefix ? prefix.size() : 0](MetricLabelIndex const& a, std::string_view b) -> bool {
361 return std::string_view(labels[a.index].label + offset, labels[a.index].size - offset) < std::string_view(b.data() + offset, b.size() - offset);
362 };
363 auto mi = std::lower_bound(rb, re, key, cmpFn);
364
365 auto miIndex = std::distance(info.metricLabelsAlphabeticallySortedIdx.begin(), mi);
366 auto miInsertionPoint = info.metricLabelsAlphabeticallySortedIdx.begin() + miIndex;
367
368 // Add the index by name in the correct position
369 // this will require moving the tail of the index,
370 // but inserting should happen only once for each metric,
371 // so who cares.
372 // Add the the actual Metric info to the store
373 MetricLabel metricLabel;
374 strncpy(metricLabel.label, name, MetricLabel::MAX_METRIC_LABEL_SIZE - 1);
375 metricLabel.label[MetricLabel::MAX_METRIC_LABEL_SIZE - 1] = '\0';
376 metricLabel.size = strlen(metricLabel.label);
377
378 // If it was already there, return the old index.
379 if (mi != re && (strncmp(info.metricLabels[mi->index].label, metricLabel.label, std::min((size_t)metricLabel.size, (size_t)MetricLabel::MAX_METRIC_LABEL_SIZE - 1)) == 0)) {
380 return mi->index;
381 }
382
383 // Add the the actual Metric info to the store
384 auto metricIndex = info.metrics.size();
385
386 // Insert the sorted location where it belongs to.
387 MetricLabelIndex metricLabelIdx{metricIndex};
388 info.metricLabelsAlphabeticallySortedIdx.insert(miInsertionPoint, metricLabelIdx);
389 auto metricInfo = createMetricInfo(info, type);
390
391 info.metricLabels.push_back(metricLabel);
392 updatePrefix(prefix, info, hasPrefix, pi);
393 info.metrics.emplace_back(metricInfo);
394
395 return metricIndex;
396}
397
399 DeviceMetricsInfo& info,
401{
402 // get the type
403 size_t metricIndex = -1;
404
405 StringMetric stringValue;
406 switch (match.type) {
408 case MetricType::Int:
410 case MetricType::Enum:
411 break;
412 case MetricType::String: {
413 auto lastChar = std::min(match.endStringValue - match.beginStringValue, StringMetric::MAX_SIZE - 1);
414 memcpy(stringValue.data, match.beginStringValue, lastChar);
415 stringValue.data[lastChar] = '\0';
416 } break;
417 default:
418 return false;
419 break;
420 };
421
422 // Find the prefix for the metric
423 auto key = std::string_view(match.beginKey, match.endKey - match.beginKey);
424
425 auto slash = key.find_first_of("/");
426 if (slash == std::string_view::npos) {
427 slash = 1;
428 }
429 auto prefix = std::string_view(key.data(), slash);
430
431 // Find the prefix.
432 auto cmpPrefixFn = [&prefixes = info.metricPrefixes](MetricPrefixIndex const& a, std::string_view b) -> bool {
433 return std::string_view(prefixes[a.index].prefix, prefixes[a.index].size) < b;
434 };
435
436 auto pi = std::lower_bound(info.metricLabelsPrefixesSortedIdx.begin(),
438 prefix,
439 cmpPrefixFn);
440 bool hasPrefix = pi != info.metricLabelsPrefixesSortedIdx.end() && (std::string_view(info.metricPrefixes[pi->index].prefix, info.metricPrefixes[pi->index].size) == prefix);
441
442 auto rb = info.metricLabelsAlphabeticallySortedIdx.begin() + (hasPrefix ? info.metricPrefixes[pi->index].begin : 0);
443 auto re = info.metricLabelsAlphabeticallySortedIdx.begin() + (hasPrefix ? info.metricPrefixes[pi->index].end : info.metricLabelsAlphabeticallySortedIdx.size());
444
445 // Find the metric based on the label. Create it if not found.
446 auto cmpFn = [&labels = info.metricLabels, offset = hasPrefix ? prefix.size() : 0](MetricLabelIndex const& a, std::string_view b) -> bool {
447 auto result = std::string_view(labels[a.index].label + offset, labels[a.index].size - offset) < std::string_view(b.data() + offset, b.size() - offset);
448 return result;
449 };
450 auto mi = std::lower_bound(rb, re, key, cmpFn);
451
452 auto miIndex = std::distance(info.metricLabelsAlphabeticallySortedIdx.begin(), mi);
453 auto miInsertionPoint = info.metricLabelsAlphabeticallySortedIdx.begin() + miIndex;
454
455 // We could not find the metric, lets insert a new one.
456 if (mi == re || (strncmp(info.metricLabels[mi->index].label, key.data(), std::min(key.size(), (size_t)MetricLabel::MAX_METRIC_LABEL_SIZE - 1)) != 0)) {
457 auto metricInfo = createMetricInfo(info, match.type);
458
459 // Add the index by name in the correct position
460 // this will require moving the tail of the index,
461 // but inserting should happen only once for each metric,
462 // so who cares.
463 MetricLabel metricLabel;
464 auto lastChar = std::min(match.endKey - match.beginKey, (ptrdiff_t)MetricLabel::MAX_METRIC_LABEL_SIZE - 1);
465 memcpy(metricLabel.label, match.beginKey, lastChar);
466 metricLabel.label[lastChar] = '\0';
467 metricLabel.size = lastChar;
468 MetricLabelIndex metricLabelIdx;
469 metricLabelIdx.index = info.metrics.size();
470 info.metricLabels.push_back(metricLabel);
471 info.metricLabelsAlphabeticallySortedIdx.insert(miInsertionPoint, metricLabelIdx);
472
473 updatePrefix(prefix, info, hasPrefix, pi);
474
475 // Add the the actual Metric info to the store
476 metricIndex = info.metrics.size();
477 assert(metricInfo.storeIdx != -1);
478 assert(metricLabel.label[0] != '\0');
479 if (newMetricsCallback != nullptr) {
480 newMetricsCallback(metricLabel.label, metricInfo, match.intValue, metricIndex);
481 }
482 info.metrics.push_back(metricInfo);
483 } else {
484 metricIndex = mi->index;
485 }
486 assert(metricIndex != -1);
487 // We are now guaranteed our metric is present at metricIndex.
488 MetricInfo& metricInfo = info.metrics[metricIndex];
489
490 // auto mod = info.timestamps[metricIndex].size();
491 auto sizeOfCollection = 0;
492 switch (metricInfo.type) {
493 case MetricType::Int: {
494 info.intMetrics[metricInfo.storeIdx][metricInfo.pos] = match.intValue;
495 sizeOfCollection = info.intMetrics[metricInfo.storeIdx].size();
496 info.intTimestamps[metricInfo.storeIdx][metricInfo.pos] = match.timestamp;
497 } break;
498 case MetricType::String: {
499 info.stringMetrics[metricInfo.storeIdx][metricInfo.pos] = stringValue;
500 sizeOfCollection = info.stringMetrics[metricInfo.storeIdx].size();
501 info.stringTimestamps[metricInfo.storeIdx][metricInfo.pos] = match.timestamp;
502 } break;
503 case MetricType::Float: {
504 info.floatMetrics[metricInfo.storeIdx][metricInfo.pos] = match.floatValue;
505 sizeOfCollection = info.floatMetrics[metricInfo.storeIdx].size();
506 info.floatTimestamps[metricInfo.storeIdx][metricInfo.pos] = match.timestamp;
507 } break;
508 case MetricType::Uint64: {
509 info.uint64Metrics[metricInfo.storeIdx][metricInfo.pos] = match.uint64Value;
510 sizeOfCollection = info.uint64Metrics[metricInfo.storeIdx].size();
511 info.uint64Timestamps[metricInfo.storeIdx][metricInfo.pos] = match.timestamp;
512 } break;
513 case MetricType::Enum: {
514 info.enumMetrics[metricInfo.storeIdx][metricInfo.pos] = match.intValue;
515 sizeOfCollection = info.enumMetrics[metricInfo.storeIdx].size();
516 info.enumTimestamps[metricInfo.storeIdx][metricInfo.pos] = match.timestamp;
517 } break;
518 default:
519 return false;
520 break;
521 };
522 // We do all the updates here, so that not update timestamps for broken metrics
523 // Notice how we always fill floatValue with the float equivalent of the metric
524 // regardless of it's type.
525 info.minDomain[metricIndex] = std::min(info.minDomain[metricIndex], (size_t)match.timestamp);
526 info.maxDomain[metricIndex] = std::max(info.maxDomain[metricIndex], (size_t)match.timestamp);
527 info.max[metricIndex] = std::max(info.max[metricIndex], match.floatValue);
528 info.min[metricIndex] = std::min(info.min[metricIndex], match.floatValue);
529 auto onlineAverage = [](float nextValue, float previousAverage, float previousCount) {
530 return previousAverage + (nextValue - previousAverage) / (previousCount + 1);
531 };
532 info.average[metricIndex] = onlineAverage(match.floatValue, info.average[metricIndex], metricInfo.filledMetrics);
533 // We point to the next metric
534 metricInfo.pos = (metricInfo.pos + 1) % sizeOfCollection;
535 ++metricInfo.filledMetrics;
536 // Note that we updated a given metric.
537 info.changed[metricIndex] = true;
538 return true;
539}
540
541size_t DeviceMetricsHelper::metricIdxByName(std::string_view const name, const DeviceMetricsInfo& info)
542{
543 size_t i = 0;
544 while (i < info.metricLabels.size()) {
545 std::string_view metricName(info.metricLabels[i].label, info.metricLabels[i].size);
546 // We check the size first and then the last character because that's
547 // likely to be different for multi-index metrics
548 if (metricName.size() == name.size() && metricName[metricName.size() - 1] == name[name.size() - 1] && metricName == name) {
549 return i;
550 }
551 ++i;
552 }
553 return i;
554}
555
556} // namespace o2::framework
benchmark::State & state
int32_t i
StringRef key
bool match(const std::vector< std::string > &queries, const char *pattern)
Definition dcs-ccdb.cxx:229
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLintptr offset
Definition glcorearb.h:660
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
Defining PrimaryVertex explicitly as messageable.
Definition TFIDInfo.h:20
std::array< size_t, metricStorageSize< T >()> TimestampsStorage
std::array< T, metricStorageSize< T >()> MetricsStorage
RuntimeErrorRef runtime_error_f(const char *,...)
static bool parseMetric(std::string_view const s, ParsedMetricMatch &results)
Helper function to parse a metric string.
static bool processMetric(ParsedMetricMatch &results, DeviceMetricsInfo &info, NewMetricCallback newMetricCallback=nullptr)
static size_t bookMetricInfo(DeviceMetricsInfo &metrics, char const *name, MetricType type)
static size_t metricIdxByName(std::string_view const name, const DeviceMetricsInfo &info)
std::function< void(std::string const &, MetricInfo const &, int value, size_t metricIndex)> NewMetricCallback
std::vector< MetricsStorage< float > > floatMetrics
std::vector< MetricsStorage< StringMetric > > stringMetrics
std::vector< std::array< size_t, metricStorageSize< int8_t >()> > enumTimestamps
std::vector< MetricsStorage< uint64_t > > uint64Metrics
std::vector< MetricsStorage< int > > intMetrics
std::vector< MetricPrefix > metricPrefixes
std::vector< std::array< size_t, metricStorageSize< float >()> > floatTimestamps
std::vector< MetricLabel > metricLabels
std::vector< std::array< size_t, metricStorageSize< uint64_t >()> > uint64Timestamps
std::vector< std::array< size_t, metricStorageSize< StringMetric >()> > stringTimestamps
std::vector< MetricPrefixIndex > metricLabelsPrefixesSortedIdx
std::vector< MetricLabelIndex > metricLabelsAlphabeticallySortedIdx
std::vector< MetricInfo > metrics
std::vector< MetricsStorage< int8_t > > enumMetrics
std::vector< std::array< size_t, metricStorageSize< int >()> > intTimestamps
char label[MAX_METRIC_LABEL_SIZE]
static constexpr size_t MAX_METRIC_LABEL_SIZE
char prefix[MAX_METRIC_PREFIX_SIZE]
Temporary struct to hold a metric after it has been parsed.
static constexpr ptrdiff_t MAX_SIZE