TensorFlow Serving C++ API Documentation
prometheus_exporter.cc
1 /* Copyright 2018 Google Inc. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7  http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow_serving/util/prometheus_exporter.h"
17 
18 #include <memory>
19 #include <vector>
20 
21 #include "absl/strings/match.h"
22 #include "absl/strings/str_cat.h"
23 #include "absl/strings/str_format.h"
24 #include "absl/strings/str_join.h"
25 #include "re2/re2.h"
26 
27 namespace tensorflow {
28 namespace serving {
29 
30 namespace {
31 
32 string SanitizeLabelValue(const string& value) {
33  // Backslash and double quote have to be escaped.
34  string new_value = value;
35  // Replace \ with \\.
36  RE2::GlobalReplace(&new_value, "\\\\", "\\\\\\\\");
37  // Replace " with \".
38  RE2::GlobalReplace(&new_value, "\"", "\\\\\"");
39  return new_value;
40 }
41 
42 string SanatizeLabelName(const string& name) {
43  // Valid format: [a-zA-Z_][a-zA-Z0-9_]*
44  string new_name = name;
45  RE2::GlobalReplace(&new_name, "[^a-zA-Z0-9]", "_");
46  if (RE2::FullMatch(new_name, "^[0-9].*")) {
47  // Start with 0-9, prepend a underscore.
48  new_name = absl::StrCat("_", new_name);
49  }
50  return new_name;
51 }
52 
53 string GetPrometheusMetricName(
54  const monitoring::MetricDescriptor& metric_descriptor) {
55  // Valid format: [a-zA-Z_:][a-zA-Z0-9_:]*
56  string new_name = metric_descriptor.name;
57  RE2::GlobalReplace(&new_name, "[^a-zA-Z0-9_]", ":");
58  if (RE2::FullMatch(new_name, "^[0-9].*")) {
59  // Start with 0-9, prepend a underscore.
60  new_name = absl::StrCat("_", new_name);
61  }
62  return new_name;
63 }
64 
65 void SerializeHistogram(const monitoring::MetricDescriptor& metric_descriptor,
66  const monitoring::PointSet& point_set,
67  std::vector<string>* lines) {
68  // For a metric name NAME, we should output:
69  // NAME_bucket{le=b1} x1
70  // NAME_bucket{le=b2} x2
71  // NAME_bucket{le=b3} x3 ...
72  // NAME_sum xsum
73  // NAME_count xcount
74  string prom_metric_name = GetPrometheusMetricName(metric_descriptor);
75  // Type definition line.
76  lines->push_back(absl::StrFormat("# TYPE %s histogram", prom_metric_name));
77  for (const auto& point : point_set.points) {
78  // Each points has differnet label values.
79  std::vector<string> labels = {};
80  labels.reserve(point->labels.size());
81  for (const auto& label : point->labels) {
82  labels.push_back(absl::StrFormat("%s=\"%s\"",
83  SanatizeLabelName(label.name),
84  SanitizeLabelValue(label.value)));
85  }
86  int64_t cumulative_count = 0;
87  string bucket_prefix =
88  absl::StrCat(prom_metric_name, "_bucket{", absl::StrJoin(labels, ","));
89  if (!labels.empty()) {
90  absl::StrAppend(&bucket_prefix, ",");
91  }
92  // One bucket per line, last one should be le="Inf".
93  for (int i = 0; i < point->histogram_value.bucket_size(); i++) {
94  cumulative_count += point->histogram_value.bucket(i);
95  string bucket_limit =
96  (i < point->histogram_value.bucket_size() - 1)
97  ? absl::StrCat(point->histogram_value.bucket_limit(i))
98  : "+Inf";
99  lines->push_back(absl::StrCat(
100  bucket_prefix, absl::StrFormat("le=\"%s\"} ", bucket_limit),
101  cumulative_count));
102  }
103  // _sum and _count.
104  lines->push_back(absl::StrCat(prom_metric_name, "_sum{",
105  absl::StrJoin(labels, ","), "} ",
106  point->histogram_value.sum()));
107  lines->push_back(absl::StrCat(prom_metric_name, "_count{",
108  absl::StrJoin(labels, ","), "} ",
109  cumulative_count));
110  }
111 }
112 
113 void SerializeScalar(const monitoring::MetricDescriptor& metric_descriptor,
114  const monitoring::PointSet& point_set,
115  std::vector<string>* lines) {
116  // A counter or gauge metric.
117  // The format should be:
118  // NAME{label=value,label=value} x time
119  string prom_metric_name = GetPrometheusMetricName(metric_descriptor);
120  string metric_type_str = "untyped";
121  if (metric_descriptor.metric_kind == monitoring::MetricKind::kCumulative) {
122  metric_type_str = "counter";
123  } else if (metric_descriptor.metric_kind == monitoring::MetricKind::kGauge) {
124  metric_type_str = "gauge";
125  }
126  // Type definition line.
127  lines->push_back(
128  absl::StrFormat("# TYPE %s %s", prom_metric_name, metric_type_str));
129  for (const auto& point : point_set.points) {
130  // Each points has differnet label values.
131  string name_bracket = absl::StrCat(prom_metric_name, "{");
132  std::vector<string> labels = {};
133  labels.reserve(point->labels.size());
134  for (const auto& label : point->labels) {
135  labels.push_back(absl::StrFormat("%s=\"%s\"",
136  SanatizeLabelName(label.name),
137  SanitizeLabelValue(label.value)));
138  }
139  lines->push_back(absl::StrCat(name_bracket, absl::StrJoin(labels, ","),
140  absl::StrFormat("} %d", point->int64_value)));
141  }
142 }
143 
144 void SerializeMetric(const monitoring::MetricDescriptor& metric_descriptor,
145  const monitoring::PointSet& point_set,
146  std::vector<string>* lines) {
147  if (metric_descriptor.value_type == monitoring::ValueType::kHistogram) {
148  SerializeHistogram(metric_descriptor, point_set, lines);
149  } else {
150  SerializeScalar(metric_descriptor, point_set, lines);
151  }
152 }
153 
154 } // namespace
155 
156 const char* const PrometheusExporter::kPrometheusPath =
157  "/monitoring/prometheus/metrics";
158 
159 PrometheusExporter::PrometheusExporter()
160  : collection_registry_(monitoring::CollectionRegistry::Default()) {}
161 
162 Status PrometheusExporter::GeneratePage(string* http_page) {
163  if (http_page == nullptr) {
164  return Status(
165  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
166  "Http page pointer is null");
167  }
168  monitoring::CollectionRegistry::CollectMetricsOptions collect_options;
169  collect_options.collect_metric_descriptors = true;
170  const std::unique_ptr<monitoring::CollectedMetrics> collected_metrics =
171  collection_registry_->CollectMetrics(collect_options);
172 
173  const auto& descriptor_map = collected_metrics->metric_descriptor_map;
174  const auto& metric_map = collected_metrics->point_set_map;
175 
176  std::vector<string> lines;
177  for (const auto& name_and_metric_descriptor : descriptor_map) {
178  const string& metric_name = name_and_metric_descriptor.first;
179  auto metric_iterator = metric_map.find(metric_name);
180  if (metric_iterator == metric_map.end()) {
181  // Not found.
182  continue;
183  }
184  SerializeMetric(*name_and_metric_descriptor.second,
185  *(metric_iterator->second), &lines);
186  }
187  *http_page = absl::StrJoin(lines, "\n");
188  absl::StrAppend(http_page, "\n");
189  return absl::OkStatus();
190 }
191 
192 } // namespace serving
193 } // namespace tensorflow