diff options
author | Tor Brede Vekterli <vekterli@vespa.ai> | 2024-03-21 11:46:14 +0000 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@vespa.ai> | 2024-03-21 12:21:26 +0000 |
commit | 1562ae1726453a0789aabb4baad2754020579b8d (patch) | |
tree | d702db3a02c9b035d4a14ee94b295f27859d6743 /metrics | |
parent | 723d6cacbdce4c45e01c92cb3e2eeb71f7b513f2 (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.cpp | 18 | ||||
-rw-r--r-- | metrics/src/vespa/metrics/state_api_adapter.cpp | 62 | ||||
-rw-r--r-- | metrics/src/vespa/metrics/state_api_adapter.h | 8 |
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 |