summaryrefslogtreecommitdiffstats
path: root/metrics
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@vespa.ai>2024-03-21 11:46:14 +0000
committerTor Brede Vekterli <vekterli@vespa.ai>2024-03-21 12:21:26 +0000
commit1562ae1726453a0789aabb4baad2754020579b8d (patch)
treed702db3a02c9b035d4a14ee94b295f27859d6743 /metrics
parent723d6cacbdce4c45e01c92cb3e2eeb71f7b513f2 (diff)
Wire Prometheus metric export to state V1 APIs
Extends metric producer classes with the requested exposition format. As a consequence, the State API server has been changed to allow emitting other content types than just `application/json`. Add custom Prometheus rendering for Slobrok, as it does its own domain-specific metric tracking. However, since it has non-destructive sampling properties, we can actually use proper `counter` types.
Diffstat (limited to 'metrics')
-rw-r--r--metrics/src/tests/metricmanagertest.cpp18
-rw-r--r--metrics/src/vespa/metrics/state_api_adapter.cpp62
-rw-r--r--metrics/src/vespa/metrics/state_api_adapter.h8
3 files changed, 64 insertions, 24 deletions
diff --git a/metrics/src/tests/metricmanagertest.cpp b/metrics/src/tests/metricmanagertest.cpp
index e5e39bd9dcb..9629c63f333 100644
--- a/metrics/src/tests/metricmanagertest.cpp
+++ b/metrics/src/tests/metricmanagertest.cpp
@@ -572,7 +572,7 @@ TEST_F(MetricManagerTest, test_json_output)
// No snapshots have been taken yet, so the non-total getMetrics call should return
// the empty string (i.e. no metrics produced).
metrics::StateApiAdapter adapter(mm);
- auto json_str = adapter.getMetrics("snapper");
+ auto json_str = adapter.getMetrics("snapper", vespalib::MetricsProducer::ExpositionFormat::JSON);
EXPECT_EQ(json_str, "");
}
@@ -635,9 +635,9 @@ TEST_F(MetricManagerTest, test_json_output)
EXPECT_EQ(10.0, slime.get()["values"][10]["values"]["last"].asDouble()) << jsonData;
metrics::StateApiAdapter adapter(mm);
- vespalib::string normal = adapter.getMetrics("snapper");
+ vespalib::string normal = adapter.getMetrics("snapper", vespalib::MetricsProducer::ExpositionFormat::JSON);
EXPECT_EQ(vespalib::string(jsonData), normal);
- vespalib::string total = adapter.getTotalMetrics("snapper");
+ vespalib::string total = adapter.getTotalMetrics("snapper", vespalib::MetricsProducer::ExpositionFormat::JSON);
EXPECT_GT(total.size(), 0);
EXPECT_NE(total, normal);
}
@@ -1058,6 +1058,18 @@ TEST_F(MetricManagerTest, prometheus_output_can_emit_inf_values_verbatim) {
EXPECT_THAT(actual, HasSubstr("outer_temp_val_sum{foo=\"baz\",fancy=\"stuff\"} -Inf 1300000\n"));
}
+TEST_F(MetricManagerTest, state_adapter_can_output_prometheus_format) {
+ SameNamesTestMetricSet mset;
+ mset.set1.val.addValue(2);
+ mset.set2.val.addValue(3);
+ MetricSnapshotTestFixture fixture(*this, mset);
+ fixture.takeSnapshotsOnce();
+ metrics::StateApiAdapter adapter(fixture.manager);
+ auto metrics = adapter.getMetrics("snapper", vespalib::MetricsProducer::ExpositionFormat::Prometheus);
+ EXPECT_THAT(metrics, HasSubstr("outer_temp_val_sum{foo=\"bar\",fancy=\"stuff\"} 2 1300000\n"));
+ EXPECT_THAT(metrics, HasSubstr("outer_temp_val_sum{foo=\"baz\",fancy=\"stuff\"} 3 1300000\n"));
+}
+
struct SneakyNamesMetricSet : public MetricSet {
DoubleValueMetric val1;
DoubleValueMetric val2;
diff --git a/metrics/src/vespa/metrics/state_api_adapter.cpp b/metrics/src/vespa/metrics/state_api_adapter.cpp
index 56a04542345..2c92448fe95 100644
--- a/metrics/src/vespa/metrics/state_api_adapter.cpp
+++ b/metrics/src/vespa/metrics/state_api_adapter.cpp
@@ -1,30 +1,45 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "jsonwriter.h"
-#include "state_api_adapter.h"
#include "metricmanager.h"
+#include "prometheus_writer.h"
+#include "state_api_adapter.h"
#include <vespa/vespalib/stllike/asciistream.h>
namespace metrics {
vespalib::string
-StateApiAdapter::getMetrics(const vespalib::string &consumer)
+StateApiAdapter::getMetrics(const vespalib::string &consumer, ExpositionFormat format)
{
MetricLockGuard guard(_manager.getMetricLock());
auto periods = _manager.getSnapshotPeriods(guard);
if (periods.empty() || !_manager.any_snapshots_taken(guard)) {
return ""; // no configuration or snapshots yet
}
- const MetricSnapshot &snapshot(_manager.getMetricSnapshot(guard, periods[0]));
- vespalib::asciistream json;
- vespalib::JsonStream stream(json);
- metrics::JsonWriter metricJsonWriter(stream);
- _manager.visit(guard, snapshot, metricJsonWriter, consumer);
- stream.finalize();
- return json.str();
+ const MetricSnapshot& snapshot(_manager.getMetricSnapshot(guard, periods[0]));
+ vespalib::asciistream out;
+ // Using `switch` instead of `if` so that we fail with a compiler warning -> error if
+ // we add another enum value and forget to add a case for it here.
+ switch (format) {
+ case ExpositionFormat::JSON:
+ {
+ vespalib::JsonStream stream(out);
+ JsonWriter metricJsonWriter(stream);
+ _manager.visit(guard, snapshot, metricJsonWriter, consumer);
+ stream.finalize();
+ break;
+ }
+ case ExpositionFormat::Prometheus:
+ {
+ PrometheusWriter writer(out);
+ _manager.visit(guard, snapshot, writer, consumer);
+ break;
+ }
+ }
+ return out.str();
}
vespalib::string
-StateApiAdapter::getTotalMetrics(const vespalib::string &consumer)
+StateApiAdapter::getTotalMetrics(const vespalib::string &consumer, ExpositionFormat format)
{
_manager.updateMetrics();
MetricLockGuard guard(_manager.getMetricLock());
@@ -34,13 +49,26 @@ StateApiAdapter::getTotalMetrics(const vespalib::string &consumer)
_manager.getTotalMetricSnapshot(guard).getMetrics(), true);
_manager.getActiveMetrics(guard).addToSnapshot(*generated, false, currentTime);
generated->setFromTime(_manager.getTotalMetricSnapshot(guard).getFromTime());
- const MetricSnapshot &snapshot = *generated;
- vespalib::asciistream json;
- vespalib::JsonStream stream(json);
- metrics::JsonWriter metricJsonWriter(stream);
- _manager.visit(guard, snapshot, metricJsonWriter, consumer);
- stream.finalize();
- return json.str();
+ const MetricSnapshot& snapshot = *generated;
+ vespalib::asciistream out;
+ switch (format) {
+ case ExpositionFormat::JSON:
+ {
+ vespalib::JsonStream stream(out);
+ metrics::JsonWriter metricJsonWriter(stream);
+ _manager.visit(guard, snapshot, metricJsonWriter, consumer);
+ stream.finalize();
+ break;
+ }
+ case ExpositionFormat::Prometheus:
+ {
+ PrometheusWriter writer(out);
+ _manager.visit(guard, snapshot, writer, consumer);
+ break;
+ }
+ }
+
+ return out.str();
}
} // namespace metrics
diff --git a/metrics/src/vespa/metrics/state_api_adapter.h b/metrics/src/vespa/metrics/state_api_adapter.h
index fd610226fda..39a80099355 100644
--- a/metrics/src/vespa/metrics/state_api_adapter.h
+++ b/metrics/src/vespa/metrics/state_api_adapter.h
@@ -11,7 +11,7 @@ class MetricManager;
/**
* This is an adapter class that implements the metrics producer
* interface defined by the state api implementation in vespalib by
- * extracting metrics in json format from a metric manager.
+ * extracting metrics in JSON or Prometheus format from a metric manager.
**/
class StateApiAdapter : public vespalib::MetricsProducer
{
@@ -19,10 +19,10 @@ private:
MetricManager &_manager;
public:
- StateApiAdapter(MetricManager &manager) : _manager(manager) {}
+ explicit StateApiAdapter(MetricManager &manager) : _manager(manager) {}
- vespalib::string getMetrics(const vespalib::string &consumer) override;
- vespalib::string getTotalMetrics(const vespalib::string &consumer) override;
+ vespalib::string getMetrics(const vespalib::string &consumer, ExpositionFormat format) override;
+ vespalib::string getTotalMetrics(const vespalib::string &consumer, ExpositionFormat format) override;
};
} // namespace metrics