From 91bb360860b4b3d7b04e86f13811fd923a7eefb9 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 30 Nov 2023 14:11:00 +0200 Subject: [PATCH] fix labels handling * collector respects custom_labels from client * instrumentation merge metric_labels into labels --- .../ext/instrumentation/base_stats.rb | 8 +- .../ext/server/stats_collector.rb | 2 +- .../ext/instrumentation/base_stats_spec.rb | 6 +- .../ext/server/stats_collector_spec.rb | 123 ++++++++++++++++++ spec/spec_helper.rb | 13 ++ 5 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 spec/prometheus_exporter/ext/server/stats_collector_spec.rb diff --git a/lib/prometheus_exporter/ext/instrumentation/base_stats.rb b/lib/prometheus_exporter/ext/instrumentation/base_stats.rb index 80ee12e..33ed052 100644 --- a/lib/prometheus_exporter/ext/instrumentation/base_stats.rb +++ b/lib/prometheus_exporter/ext/instrumentation/base_stats.rb @@ -29,12 +29,12 @@ def collect_data(data) @client.send_json(metric) end - # @param datum [Hash] + # @param data [Hash] # @return [Hash] - def build_metric(datum) - metric = datum.dup + def build_metric(data) + metric = data.dup metric[:type] = type - metric[:metric_labels] = @metric_labels + metric[:labels] = (metric[:labels] || {}).merge(@metric_labels) metric end end diff --git a/lib/prometheus_exporter/ext/server/stats_collector.rb b/lib/prometheus_exporter/ext/server/stats_collector.rb index ec0d616..a114be1 100644 --- a/lib/prometheus_exporter/ext/server/stats_collector.rb +++ b/lib/prometheus_exporter/ext/server/stats_collector.rb @@ -59,7 +59,7 @@ def fill_observers(obj, labels) def build_labels(obj) labels = {} labels.merge!(obj['labels']) if obj['labels'] - labels.merge!(obj['metric_labels']) if obj['metric_labels'] + labels.merge!(obj['custom_labels']) if obj['custom_labels'] labels end diff --git a/spec/prometheus_exporter/ext/instrumentation/base_stats_spec.rb b/spec/prometheus_exporter/ext/instrumentation/base_stats_spec.rb index 9b4ab50..b92a530 100644 --- a/spec/prometheus_exporter/ext/instrumentation/base_stats_spec.rb +++ b/spec/prometheus_exporter/ext/instrumentation/base_stats_spec.rb @@ -27,14 +27,12 @@ { type: 'test', foo: 123, bar: 456, - labels: { qwe: 'asd' }, - metric_labels: {} + labels: { qwe: 'asd' } }, { type: 'test', foo: 124, bar: 457, - labels: { qwe: 'zxc' }, - metric_labels: {} + labels: { qwe: 'zxc' } } ] ) diff --git a/spec/prometheus_exporter/ext/server/stats_collector_spec.rb b/spec/prometheus_exporter/ext/server/stats_collector_spec.rb new file mode 100644 index 0000000..d82482a --- /dev/null +++ b/spec/prometheus_exporter/ext/server/stats_collector_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +RSpec.describe PrometheusExporter::Ext::Server::StatsCollector do + subject do + collector.collect(stringified_metric) + end + + let(:collector) { TestCollector.new } + let(:stringified_metric) { JSON.parse JSON.generate(metric) } + let(:metric) do + { + type: 'test', + labels: { qwe: 'asd' }, + g_metric: 1.23, + gwt_metric: 4.56, + c_metric: 7.89 + } + end + let(:expected_labels) { metric[:labels] } + + it 'observes prometheus metrics' do + subject + expect(collector.metrics).to contain_exactly( + a_gauge_metric('test_g_metric').with(1.23, expected_labels), + a_gauge_with_time_metric('test_gwt_metric').with([4.56, ms_since_epoch], expected_labels), + a_counter_metric('test_c_metric').with(7.89, expected_labels) + ) + end + + context 'with empty custom_labels' do + let(:metric) do + super().merge custom_labels: {} + end + + it 'observes prometheus metrics' do + subject + expect(collector.metrics).to contain_exactly( + a_gauge_metric('test_g_metric').with(1.23, expected_labels), + a_gauge_with_time_metric('test_gwt_metric').with([4.56, ms_since_epoch], + expected_labels + ), + a_counter_metric('test_c_metric').with(7.89, expected_labels) + ) + end + end + + context 'with filled custom_labels' do + let(:metric) do + super().merge custom_labels: { host: 'example.com' } + end + let(:expected_labels) { metric[:labels].merge metric[:custom_labels] } + + it 'observes prometheus metrics' do + subject + expect(collector.metrics).to contain_exactly( + a_gauge_metric('test_g_metric').with(1.23, expected_labels), + a_gauge_with_time_metric('test_gwt_metric').with([4.56, ms_since_epoch], expected_labels), + a_counter_metric('test_c_metric').with(7.89, expected_labels) + ) + end + end + + context 'when collector has previous metrics with same labels' do + let(:prev_stringified_metric) { JSON.parse JSON.generate(prev_metric) } + let(:prev_metric) do + { + type: 'test', + labels: { qwe: 'asd' }, + g_metric: 10, + gwt_metric: 20, + c_metric: 30 + } + end + + before do + collector.collect(prev_stringified_metric) + end + + it 'observes prometheus metrics' do + subject + expect(collector.metrics).to contain_exactly( + a_gauge_metric('test_g_metric').with(1.23, expected_labels), + a_gauge_with_time_metric('test_gwt_metric').with([4.56, ms_since_epoch], + expected_labels + ), + a_counter_metric('test_c_metric').with(37.89, expected_labels) + ) + end + end + + context 'when collector has previous metrics with different labels' do + let(:prev_stringified_metric) { JSON.parse JSON.generate(prev_metric) } + let(:prev_metric) do + { + type: 'test', + labels: { qwe: 'asd2' }, + g_metric: 10, + gwt_metric: 20, + c_metric: 30 + } + end + let(:prev_expected_labels) { prev_metric[:labels] } + + before do + collector.collect(prev_stringified_metric) + end + + it 'observes prometheus metrics' do + subject + expect(collector.metrics).to contain_exactly( + a_gauge_metric('test_g_metric') + .with(10, prev_expected_labels) + .with(1.23, expected_labels), + a_gauge_with_time_metric('test_gwt_metric') + .with([20, ms_since_epoch], prev_expected_labels) + .with([4.56, ms_since_epoch], expected_labels), + a_counter_metric('test_c_metric') + .with(30, prev_expected_labels) + .with(7.89, expected_labels) + ) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 27da4b5..ffe23ff 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true require 'prometheus_exporter' +require 'prometheus_exporter/server' require 'prometheus_exporter/ext' require 'prometheus_exporter/ext/rspec' require 'prometheus_exporter/ext/instrumentation/base_stats' +require 'prometheus_exporter/ext/server/stats_collector' + +RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil class TestInstrumentation < PrometheusExporter::Ext::Instrumentation::BaseStats self.type = 'test' @@ -15,6 +19,15 @@ def collect(data_list) end end +class TestCollector < PrometheusExporter::Server::TypeCollector + include PrometheusExporter::Ext::Server::StatsCollector + self.type = 'test' + + register_metric :g_metric, :gauge, 'test gauge metric' + register_metric :gwt_metric, :gauge_with_time, 'test gauge with time metric' + register_metric :c_metric, :counter, 'test counter metric' +end + RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status'