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
40using 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 */
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}
virtual ~MetricsRequestProcessor()
Destructor.
fawkes::WebReply * process_request(const fawkes::WebRequest *request)
Process request.
MetricsRequestProcessor(fawkes::MetricsManager *manager, fawkes::Logger *logger, const std::string &base_url)
Constructor.
Interface for logging.
Definition: logger.h:42
Base class for metrics managers.
virtual std::list< io::prometheus::client::MetricFamily > all_metrics()=0
Get combination of all metrics.
Static web reply.
Definition: reply.h:136
void append_body(const char *format,...)
Append to body.
Definition: reply.cpp:253
Basic web reply.
Definition: reply.h:34
void set_code(Code code)
Set response code.
Definition: reply.cpp:113
void add_header(const std::string &header, const std::string &content)
Add a HTTP header.
Definition: reply.cpp:123
Web request meta data carrier.
Definition: request.h:42
bool has_header(std::string key) const
Check if the named header value has been received.
Definition: request.h:256
std::string header(std::string &key) const
Header specific header value.
Definition: request.h:236
Fawkes library namespace.