diff --git a/common/include/villas/hist.hpp b/common/include/villas/hist.hpp index 3c7acb33e..5bffa1137 100644 --- a/common/include/villas/hist.hpp +++ b/common/include/villas/hist.hpp @@ -7,8 +7,9 @@ #pragma once +#include #include - +#include #include #include @@ -49,6 +50,7 @@ class Hist { // Print ASCII style plot of histogram. void plot(Logger logger) const; + void test(); // Dump histogram data in Matlab format. // // @return The string containing the dump. The caller is responsible to free() the buffer. @@ -60,6 +62,9 @@ class Hist { // Write the histogram in JSON format to file \p f. int dumpJson(FILE *f) const; + std::string toPrometheusText(std::string metric_name, + std::string node_name) const; + // Build a libjansson / JSON object of the histogram. json_t *toJson() const; diff --git a/common/lib/hist.cpp b/common/lib/hist.cpp index d2ad73df9..969dbe62f 100644 --- a/common/lib/hist.cpp +++ b/common/lib/hist.cpp @@ -150,6 +150,31 @@ void Hist::plot(Logger logger) const { } } +std::string Hist::toPrometheusText(std::string metric_name, + std::string node_name) const { + std::stringstream base; + base << "#TYPE HISTOGRAM " << metric_name; + // Needed because Prometheus understands quantiles. + int s = data.size(); + Hist::cnt_t cumsum = 0; + for (int i = 0; i < s; i++) { + cumsum += data[i]; + base << "\n" + << metric_name << " {node=\"" << node_name << "\" le=\"" + << ((double)i + 1) / s << "\"} " << cumsum; + } + + base << "\n" + << metric_name << " {node=\"" << node_name << "\" le=\"+Inf\"} " << total + << "\n" + << metric_name << "_count " + << " {node=\"" << node_name << "\"} " << total; + + //next line is problematic because buckets have no associated values ? + //base +="\n"+metric_name+"_count "+" {node=\""+node_name+"} "+std::to_string(ttl); + return base.str(); +} + char *Hist::dump() const { char *buf = new char[128]; if (!buf) diff --git a/lib/api/CMakeLists.txt b/lib/api/CMakeLists.txt index b52e3eee1..70974bdeb 100644 --- a/lib/api/CMakeLists.txt +++ b/lib/api/CMakeLists.txt @@ -26,6 +26,7 @@ set(API_SRC requests/node_stats.cpp requests/node_stats_reset.cpp requests/node_file.cpp + requests/metrics.cpp requests/paths.cpp requests/path_info.cpp requests/path_action.cpp diff --git a/lib/api/requests/metrics.cpp b/lib/api/requests/metrics.cpp new file mode 100644 index 000000000..d5977cccc --- /dev/null +++ b/lib/api/requests/metrics.cpp @@ -0,0 +1,65 @@ +/* The metrics API ressource. + * + * Author: Steffen Vogel + * Author: Youssef Nakti + * SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace villas { +namespace node { +namespace api { + +class MetricsRequest : public Request { +public: + using Request::Request; + + virtual Response *execute() { + if (method != Session::Method::GET) + throw InvalidMethod(this); + + if (body != nullptr) + throw BadRequest("The metrics endpoint does not accept any body data"); + + std::stringstream res_stream; + NodeList node_list = session->getSuperNode()->getNodes(); + for (Node *node : node_list) { + auto stats = node->getStats(); + if (!stats) + continue; + std::string node_name = node->getNameShort(); + for (auto &metric : Stats::metrics) { + std::string metric_name = metric.second.name; + std::replace(metric_name.begin(), metric_name.end(), '.', '_'); + res_stream << stats->getHistogram(metric.first) + .toPrometheusText(metric_name, node->getNameShort()) + << "\n"; + } + } + auto text_res = res_stream.str(); + return new Response(session, HTTP_STATUS_OK, "text/plain; charset=UTF-8", + Buffer(text_res.c_str(), text_res.size())); + } +}; + +// Register API request +static char n[] = "metrics"; +static char r[] = "/metrics"; +static char d[] = "Get stats of all nodes in Prometheus metrics format"; +static RequestPlugin p; + +} // namespace api +} // namespace node +} // namespace villas