Fawkes API  Fawkes Development Version
metrics_processor.cpp
1 
2 /***************************************************************************
3  * metrics_processor.cpp - Metrics exporter for prometheus request processor
4  *
5  * Created: Sat May 06 19:48:50 2017 (German Open 2017)
6  * Copyright 2017 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include "metrics_processor.h"
23 
24 #include "aspect/metrics_manager.h"
25 
26 #include <logging/logger.h>
27 #include <webview/page_reply.h>
28 #include <webview/request.h>
29 
30 #include <sstream>
31 #if GOOGLE_PROTOBUF_VERSION >= 3000000
32 # include <google/protobuf/util/json_util.h>
33 # include <google/protobuf/util/message_differencer.h>
34 #endif
35 #include <google/protobuf/io/coded_stream.h>
36 #include <google/protobuf/io/zero_copy_stream_impl.h>
37 
38 #include <algorithm>
39 
40 using namespace fawkes;
41 
42 /** @class MetricsRequestProcessor "metrics_processor.h"
43  * Metrics web request processor.
44  * Process web requests to the metrics URL space.
45  * @author Tim Niemueller
46  */
47 
48 /** Constructor.
49  * @param manager metrics manager
50  * @param logger logger to report problems
51  * @param baseurl base URL of the RRD webrequest processor
52  */
54  fawkes::Logger * logger,
55  const std::string &baseurl)
56 : metrics_manager_(metrics_manager), logger_(logger), base_url_(baseurl)
57 {
58 }
59 
60 /** Destructor. */
62 {
63 }
64 
65 /** Process request.
66  * @param request incoming request
67  * @return web reply
68  */
69 WebReply *
71 {
72  std::string accepted_encoding = "text/plain";
73  if (request->has_header("Accept")) {
74  accepted_encoding = request->header("Accept");
75  }
76 
77  // std::string subpath = request->url().substr(base_url_.length());
78  StaticWebReply *reply = new StaticWebReply(WebReply::HTTP_OK);
79 
80  std::list<io::prometheus::client::MetricFamily> metrics(
81  std::move(metrics_manager_->all_metrics()));
82 
83  if (accepted_encoding.find("application/vnd.google.protobuf") != std::string::npos) {
84  reply->add_header("Content-type",
85  "application/vnd.google.protobuf; "
86  "proto=io.prometheus.client.MetricFamily; "
87  "encoding=delimited");
88  std::ostringstream ss;
89  for (auto &&metric : metrics) {
90  {
91  google::protobuf::io::OstreamOutputStream raw_output{&ss};
92  google::protobuf::io::CodedOutputStream output(&raw_output);
93 
94  const int size = metric.ByteSize();
95  output.WriteVarint32(size);
96  }
97 
98  std::string buffer;
99  metric.SerializeToString(&buffer);
100  ss << buffer;
101  }
102 
103  reply->append_body(ss.str());
104  } else if (accepted_encoding.find("application/json") != std::string::npos) {
105 #if GOOGLE_PROTOBUF_VERSION >= 3000000
106  reply->add_header("Content-type", "application/json");
107  std::stringstream ss;
108  ss << "[";
109 
110  for (auto &&metric : metrics) {
111  std::string result;
112  google::protobuf::util::MessageToJsonString(metric,
113  &result,
114  google::protobuf::util::JsonPrintOptions());
115  ss << result;
116  if (!google::protobuf::util::MessageDifferencer::Equals(metric, metrics.back())) {
117  ss << ",";
118  }
119  }
120  ss << "]";
121  reply->append_body("%s", ss.str().c_str());
122 #else
123  reply->set_code(WebReply::HTTP_NOT_IMPLEMENTED);
124  reply->add_header("Content-type", "text/plain");
125  reply->append_body("JSON output only supported with protobuf 3");
126 #endif
127  } else {
128  reply->add_header("Content-type", "text/plain; version=0.0.4");
129  reply->append_body("# Fawkes Metrics\n");
130  for (auto &&metric : metrics) {
131  if (metric.metric_size() > 0) {
132  reply->append_body("\n");
133  if (metric.has_help()) {
134  reply->append_body("# HELP %s %s\n", metric.name().c_str(), metric.help().c_str());
135  }
136  if (metric.has_type()) {
137  const char *typestr = NULL;
138  switch (metric.type()) {
139  case io::prometheus::client::COUNTER: typestr = "counter"; break;
140  case io::prometheus::client::GAUGE: typestr = "gauge"; break;
141  case io::prometheus::client::UNTYPED: typestr = "untyped"; break;
142  case io::prometheus::client::HISTOGRAM: typestr = "histogram"; break;
143  case io::prometheus::client::SUMMARY: typestr = "summary"; break;
144  }
145  if (typestr != NULL) {
146  reply->append_body("# TYPE %s %s\n", metric.name().c_str(), typestr);
147  }
148  }
149  for (int i = 0; i < metric.metric_size(); ++i) {
150  const io::prometheus::client::Metric &m = metric.metric(i);
151 
152  std::string labels;
153  if (m.label_size() > 0) {
154  std::ostringstream ss;
155  ss << " {";
156  ss << m.label(0).name() << "=" << m.label(0).value();
157  for (int l = 1; l < m.label_size(); ++l) {
158  const io::prometheus::client::LabelPair &label = m.label(l);
159  ss << "," << label.name() << "=" << label.value();
160  }
161  ss << "}";
162  labels = ss.str();
163  }
164  std::string timestamp;
165  if (m.has_timestamp_ms()) {
166  timestamp = " " + std::to_string(m.timestamp_ms());
167  }
168 
169  switch (metric.type()) {
170  case io::prometheus::client::COUNTER:
171  if (m.has_counter()) {
172  reply->append_body("%s%s %f%s\n",
173  metric.name().c_str(),
174  labels.c_str(),
175  m.counter().value(),
176  timestamp.c_str());
177  } else {
178  reply->append_body("# ERROR %s%svalue not set\n",
179  metric.name().c_str(),
180  labels.c_str());
181  }
182  break;
183 
184  case io::prometheus::client::GAUGE:
185  if (m.has_gauge()) {
186  reply->append_body("%s%s %f%s\n",
187  metric.name().c_str(),
188  labels.c_str(),
189  m.gauge().value(),
190  timestamp.c_str());
191  } else {
192  reply->append_body("# ERROR %s%svalue not set\n",
193  metric.name().c_str(),
194  labels.c_str());
195  }
196  break;
197 
198  case io::prometheus::client::UNTYPED:
199  if (m.has_untyped()) {
200  reply->append_body("%s%s %f%s\n",
201  metric.name().c_str(),
202  labels.c_str(),
203  m.untyped().value(),
204  timestamp.c_str());
205  } else {
206  reply->append_body("# ERROR %s%svalue not set\n",
207  metric.name().c_str(),
208  labels.c_str());
209  }
210  break;
211 
212  case io::prometheus::client::SUMMARY:
213  if (m.has_summary()) {
214  const io::prometheus::client::Summary &summary = m.summary();
215  for (int q = 0; q < summary.quantile_size(); ++q) {
216  const io::prometheus::client::Quantile &quantile = summary.quantile(q);
217  std::string q_label;
218  if (labels.empty()) {
219  q_label = " {quantile=" + std::to_string(quantile.quantile()) + "}";
220  } else {
221  q_label = labels.substr(0, labels.size() - 1)
222  + ",quantile=" + std::to_string(quantile.quantile()) + "}";
223  }
224  reply->append_body("%s%s %f%s\n",
225  metric.name().c_str(),
226  q_label.c_str(),
227  quantile.value(),
228  timestamp.c_str());
229  }
230  reply->append_body("%s_sum%s %f%s\n",
231  metric.name().c_str(),
232  labels.c_str(),
233  summary.sample_sum(),
234  timestamp.c_str());
235  reply->append_body("%s_count%s %f%s\n",
236  metric.name().c_str(),
237  labels.c_str(),
238  summary.sample_count(),
239  timestamp.c_str());
240  } else {
241  reply->append_body("# ERROR %s%svalue not set\n",
242  metric.name().c_str(),
243  labels.c_str());
244  }
245  break;
246 
247  case io::prometheus::client::HISTOGRAM:
248  if (m.has_histogram()) {
249  const io::prometheus::client::Histogram &histogram = m.histogram();
250  for (int b = 0; b < histogram.bucket_size(); ++b) {
251  const io::prometheus::client::Bucket &bucket = histogram.bucket(b);
252  std::string b_label;
253  if (labels.empty()) {
254  b_label = " {le=" + std::to_string(bucket.upper_bound()) + "}";
255  } else {
256  b_label = labels.substr(0, labels.size() - 1)
257  + ",le=" + std::to_string(bucket.upper_bound()) + "}";
258  }
259  reply->append_body("%s%s %lu%s\n",
260  metric.name().c_str(),
261  b_label.c_str(),
262  bucket.cumulative_count(),
263  timestamp.c_str());
264  }
265  reply->append_body("%s_sum%s %f%s\n",
266  metric.name().c_str(),
267  labels.c_str(),
268  histogram.sample_sum(),
269  timestamp.c_str());
270  reply->append_body("%s_count%s %lu%s\n",
271  metric.name().c_str(),
272  labels.c_str(),
273  histogram.sample_count(),
274  timestamp.c_str());
275  } else {
276  reply->append_body("# ERROR %s%svalue not set\n",
277  metric.name().c_str(),
278  labels.c_str());
279  }
280  break;
281  }
282  }
283  }
284  }
285  }
286 
287  return reply;
288 }
MetricsRequestProcessor::~MetricsRequestProcessor
virtual ~MetricsRequestProcessor()
Destructor.
Definition: metrics_processor.cpp:61
fawkes::WebRequest
Web request meta data carrier.
Definition: request.h:42
fawkes::WebReply::set_code
void set_code(Code code)
Set response code.
Definition: reply.cpp:113
MetricsRequestProcessor::MetricsRequestProcessor
MetricsRequestProcessor(fawkes::MetricsManager *manager, fawkes::Logger *logger, const std::string &base_url)
Constructor.
Definition: metrics_processor.cpp:53
fawkes::WebRequest::has_header
bool has_header(std::string key) const
Check if the named header value has been received.
Definition: request.h:256
fawkes::Logger
Interface for logging.
Definition: logger.h:42
fawkes
Fawkes library namespace.
fawkes::StaticWebReply::append_body
void append_body(const char *format,...)
Append to body.
Definition: reply.cpp:253
fawkes::WebReply::add_header
void add_header(const std::string &header, const std::string &content)
Add a HTTP header.
Definition: reply.cpp:123
fawkes::MetricsManager::all_metrics
virtual std::list< io::prometheus::client::MetricFamily > all_metrics()=0
Get combination of all metrics.
fawkes::MetricsManager
Base class for metrics managers.
Definition: metrics_manager.h:32
fawkes::StaticWebReply
Static web reply.
Definition: reply.h:136
fawkes::WebReply
Basic web reply.
Definition: reply.h:34
MetricsRequestProcessor::process_request
fawkes::WebReply * process_request(const fawkes::WebRequest *request)
Process request.
Definition: metrics_processor.cpp:70
fawkes::WebRequest::header
std::string header(std::string &key) const
Header specific header value.
Definition: request.h:236