From c9d3e35a05d000b2b907f09c9be02cc09bbb7882 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Fri, 27 Oct 2023 12:16:50 +0200 Subject: [PATCH] Add specs --- spec/datadog/core/telemetry/client_spec.rb | 90 +++++++++ spec/datadog/core/telemetry/emitter_spec.rb | 47 ++++- spec/datadog/core/telemetry/event_spec.rb | 25 ++- .../core/telemetry/metric_queue_spec.rb | 128 ++++++++++++ spec/datadog/core/telemetry/metric_spec.rb | 184 ++++++++++++++++++ .../core/telemetry/metric_worker_spec.rb | 46 +++++ 6 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 spec/datadog/core/telemetry/metric_queue_spec.rb create mode 100644 spec/datadog/core/telemetry/metric_spec.rb create mode 100644 spec/datadog/core/telemetry/metric_worker_spec.rb diff --git a/spec/datadog/core/telemetry/client_spec.rb b/spec/datadog/core/telemetry/client_spec.rb index 6794bb699d8..07a0794899e 100644 --- a/spec/datadog/core/telemetry/client_spec.rb +++ b/spec/datadog/core/telemetry/client_spec.rb @@ -20,6 +20,8 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end context 'with default parameters' do @@ -27,6 +29,11 @@ it { is_expected.to be_a_kind_of(described_class) } it { expect(client.enabled).to be(true) } it { expect(client.emitter).to be(emitter) } + + it 'set Metric::Rate interval value' do + expect(Datadog::Core::Telemetry::Metric::Rate).to receive(:'interval=').with(heartbeat_interval_seconds) + client + end end context 'when :enabled is false' do @@ -34,6 +41,7 @@ it { is_expected.to be_a_kind_of(described_class) } it { expect(client.enabled).to be(false) } it { expect(client.worker.enabled?).to be(false) } + it { expect(client.metrics_worker.enabled?).to be(false) } end context 'when enabled' do @@ -42,6 +50,7 @@ it { is_expected.to be_a_kind_of(described_class) } it { expect(client.enabled).to be(true) } it { expect(client.worker.enabled?).to be(true) } + it { expect(client.metrics_worker.enabled?).to be(true) } end end @@ -49,10 +58,13 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end it { expect { client.disable! }.to change { client.enabled }.from(true).to(false) } it { expect { client.disable! }.to change { client.worker.enabled? }.from(true).to(false) } + it { expect { client.disable! }.to change { client.metrics_worker.enabled? }.from(true).to(false) } end describe '#started!' do @@ -61,6 +73,8 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end context 'when disabled' do @@ -127,6 +141,8 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end context 'when disabled' do @@ -208,6 +224,8 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end context 'when disabled' do @@ -248,6 +266,8 @@ after do client.worker.stop(true) client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join end context 'when disabled' do @@ -283,4 +303,74 @@ end end end + + context 'metrics' do + [ + [:add_count_metric, Datadog::Core::Telemetry::Metric::Count], + [:add_rate_metric, Datadog::Core::Telemetry::Metric::Rate], + [:add_gauge_metric, Datadog::Core::Telemetry::Metric::Gauge], + [:add_distribution_metric, Datadog::Core::Telemetry::Metric::Distribution], + ].each do |metric_method, metric_klass| + context 'when disabled' do + let(:enabled) { false } + it do + expect(client.send(metric_method, 'test_namespace', 'name', 1, {})).to be_nil + end + end + + context 'when enabled' do + let(:enabled) { true } + it do + expect_any_instance_of(Datadog::Core::Telemetry::MetricQueue).to receive(:add_metric).with( + 'test_namespace', + 'name', + 1, + {}, + metric_klass + ) + client.send(metric_method, 'test_namespace', 'name', 1, {}) + end + end + end + + describe '#flush_metrics!' do + after do + client.worker.stop(true) + client.worker.join + client.metrics_worker.stop(true) + client.metrics_worker.join + end + + context 'when disabled' do + let(:enabled) { false } + it do + expect(client.send(:flush_metrics!)).to be_nil + end + end + + context 'when enabled' do + let(:enabled) { true } + it 'send metrics to the emitter and reset the metric_queue' do + old_metric_queue = client.instance_variable_get(:@metric_queue) + + client.add_distribution_metric('test_namespace', 'name', 1, {}) + expected_payload = { + :namespace => 'test_namespace', + :series => [ + { + :metric => 'name', :tags => [], :values => [1], + :type => 'distributions', + :common => true + } + ] + } + expect(emitter).to receive(:request).with('distributions', payload: expected_payload) + client.send(:flush_metrics!) + + new_metric_queue = client.instance_variable_get(:@metric_queue) + expect(old_metric_queue).to_not eq new_metric_queue + end + end + end + end end diff --git a/spec/datadog/core/telemetry/emitter_spec.rb b/spec/datadog/core/telemetry/emitter_spec.rb index 5e8e7848335..874caff8e3d 100644 --- a/spec/datadog/core/telemetry/emitter_spec.rb +++ b/spec/datadog/core/telemetry/emitter_spec.rb @@ -84,10 +84,55 @@ it 'creates a telemetry event with data' do expect(Datadog::Core::Telemetry::Event).to receive(:new).and_return(event) - expect(event).to receive(:telemetry_request).with(request_type: request_type, seq_id: be_a(Integer), data: data) + expect(event).to receive(:telemetry_request).with( + request_type: request_type, + seq_id: be_a(Integer), + data: data, + payload: nil + ) request end end + + context 'with data and payload' do + subject(:request) { emitter.request(:'app-started', data: {}, payload: {}) } + + it 'fails to send request' do + request + expect(Datadog.logger).to have_received(:debug) do |message| + expect(message).to include('Can not provide data and payload') + end + end + end + + context 'metrics' do + let(:request_type) { 'generate-metrics' } + let(:event) { double('event') } + let(:payload) { {} } + subject(:request) { emitter.request(request_type, payload: payload) } + + it 'creates a telemetry metric with payload' do + expect(Datadog::Core::Telemetry::Event).to receive(:new).and_return(event) + expect(event).to receive(:telemetry_request).with( + request_type: request_type, + seq_id: be_a(Integer), + data: nil, + payload: payload + ) + request + end + + context 'missing payload' do + let(:payload) { nil } + + it 'fail to send metric evenet' do + request + expect(Datadog.logger).to have_received(:debug) do |message| + expect(message).to include('Unable to send telemetry request') + end + end + end + end end describe 'when initialized multiple times' do diff --git a/spec/datadog/core/telemetry/event_spec.rb b/spec/datadog/core/telemetry/event_spec.rb index f32a02b62c1..7cd57a9297d 100644 --- a/spec/datadog/core/telemetry/event_spec.rb +++ b/spec/datadog/core/telemetry/event_spec.rb @@ -14,11 +14,14 @@ end describe '#telemetry_request' do - subject(:telemetry_request) { event.telemetry_request(request_type: request_type, seq_id: seq_id, data: data) } + subject(:telemetry_request) do + event.telemetry_request(request_type: request_type, seq_id: seq_id, data: data, payload: payload) + end let(:request_type) { :'app-started' } let(:seq_id) { 1 } let(:data) { nil } + let(:payload) { nil } it { is_expected.to be_a_kind_of(Datadog::Core::Telemetry::V1::TelemetryRequest) } it { expect(telemetry_request.api_version).to eql('v1') } @@ -62,6 +65,26 @@ end end + ['generate-metrics', 'distributions'].each do |request_type| + context request_type do + let(:request_type) { request_type } + + context 'when payload is nil' do + let(:payload) { nil } + + it 'raise ArgumentError' do + expect { telemetry_request }.to raise_error(ArgumentError) + end + end + + context 'when payload is not nil' do + let(:payload) { { foo: :bar } } + + it { expect(telemetry_request.payload).to eq({ foo: :bar }) } + end + end + end + context 'is nil' do let(:request_type) { nil } it { expect { telemetry_request }.to raise_error(ArgumentError) } diff --git a/spec/datadog/core/telemetry/metric_queue_spec.rb b/spec/datadog/core/telemetry/metric_queue_spec.rb new file mode 100644 index 00000000000..d5a88ff28b8 --- /dev/null +++ b/spec/datadog/core/telemetry/metric_queue_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +require 'datadog/core/telemetry/metric_queue' +require 'datadog/core/telemetry/metric' + +RSpec.describe Datadog::Core::Telemetry::MetricQueue do + let(:metric_queue) { described_class.new } + let(:metric_klass) { Datadog::Core::Telemetry::Metric::Count } + subject(:empty_metrics_queue) do + { + 'generate-metrics' => {}, + 'distributions' => {}, + } + end + + describe '#add_metric' do + context 'no previous metric' do + it 'creates a new metric entry point and stores it' do + expect(metric_queue.metrics).to eq(empty_metrics_queue) + expect(metric_klass).to receive(:new).with('test_metric_name', { foo: :bar }).and_call_original + expect_any_instance_of(metric_klass).to receive(:update_value).with(1).and_call_original + metric_queue.add_metric( + 'test_namespace', + 'test_metric_name', + 1, + { foo: :bar }, + metric_klass + ) + + expect(metric_queue.metrics[metric_klass.request_type]['test_namespace']).to_not be_nil + expect(metric_queue.metrics[metric_klass.request_type]['test_namespace']['test_metric_name']).to_not be_nil + + metric_instace = metric_queue.metrics[metric_klass.request_type]['test_namespace']['test_metric_name'] + expect(metric_instace).to be_a(metric_klass) + end + end + + context 'previous metric' do + it 'just updates values and stores metric back' do + metric_queue.add_metric( + 'test_namespace', + 'test_metric_name', + 1, + { foo: :bar }, + metric_klass + ) + + expect(metric_klass).to_not receive(:new) + expect_any_instance_of(metric_klass).to receive(:update_value).with(2).and_call_original + + metric_queue.add_metric( + 'test_namespace', + 'test_metric_name', + 2, + { foo: :bar }, + metric_klass + ) + + metric_instace = metric_queue.metrics[metric_klass.request_type]['test_namespace']['test_metric_name'] + expect(metric_instace).to be_a(metric_klass) + end + end + end + + describe '#build_metrics_payload' do + it 'yields metric_type and assiciated payload' do + expect(Time).to receive(:now).and_return(1234) + + metric_queue.add_metric( + 'test_namespace', + 'test_metric_name', + 1, + { foo: :bar }, + metric_klass + ) + + metric_queue.add_metric( + 'test_namespace_two', + 'test_metric_name_distribution', + 1, + { foo: :bar }, + Datadog::Core::Telemetry::Metric::Distribution + ) + + expect do |b| + metric_queue.build_metrics_payload(&b) + end.to yield_successive_args( + [ + 'generate-metrics', { + :namespace => 'test_namespace', + :series => + [ + { + :metric => 'test_metric_name', + :tags => ['foo:bar'], + :values => [[1234, 1]], + :type => 'count', + :common => true + } + ] + } + ], + [ + 'distributions', { + :namespace => 'test_namespace_two', + :series => [ + { + :metric => 'test_metric_name_distribution', + :tags => ['foo:bar'], + :values => [1], + :type => 'distributions', + :common => true + } + ] + } + ] + ) + end + + context 'empty metrics' do + it 'does not yield information' do + expect do |b| + metric_queue.build_metrics_payload(&b) + end.not_to yield_control + end + end + end +end diff --git a/spec/datadog/core/telemetry/metric_spec.rb b/spec/datadog/core/telemetry/metric_spec.rb new file mode 100644 index 00000000000..2eb8272c2c8 --- /dev/null +++ b/spec/datadog/core/telemetry/metric_spec.rb @@ -0,0 +1,184 @@ +require 'spec_helper' + +require 'datadog/core/telemetry/metric' + +RSpec.describe Datadog::Core::Telemetry::Metric::Count do + subject(:metric) { described_class.new('tests', { foo: :bar }) } + let(:generate_metric_type) { 'generate-metrics' } + + describe '.request_type' do + subject(:request_type) { metric.class.request_type } + + it { is_expected.to eq(generate_metric_type) } + end + + describe '#update_value' do + it 'increment values' do + expect(metric.values).to be_nil + metric.update_value(1) + expect(metric.values).to_not be_nil + expect(metric.values[0][1]).to eq 1 + metric.update_value(1) + expect(metric.values[0][1]).to eq 2 + end + + it 'stores the timestamp as part of the values array' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + expect(metric.values[0][0]).to eq 1234 + end + end + + describe '#metric_type' do + subject(:request_type) { metric.metric_type } + + it { is_expected.to eq('count') } + end + + describe '#to_h' do + it 'returns the metric information' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + + result = metric.to_h + expected = { :common => true, :tags => ['foo:bar'], :type => 'count', :values => [[1234, 1]] } + expect(result).to eq(expected) + end + end +end + +RSpec.describe Datadog::Core::Telemetry::Metric::Rate do + before do + described_class.interval = 10.0 + end + + after do + described_class.interval = nil + end + + subject(:metric) { described_class.new('tests', { foo: :bar }) } + let(:generate_metric_type) { 'generate-metrics' } + + describe '.request_type' do + subject(:request_type) { metric.class.request_type } + + it { is_expected.to eq(generate_metric_type) } + end + + describe '#update_value' do + it 'calculate rate value using interval' do + expect(metric.values).to be_nil + metric.update_value(1) + expect(metric.values).to_not be_nil + expect(metric.values[0][1]).to eq 0.1 + metric.update_value(1) + expect(metric.values[0][1]).to eq 0.2 + end + + it 'stores the timestamp as part of the values array' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + expect(metric.values[0][0]).to eq 1234 + end + end + + describe '#metric_type' do + subject(:request_type) { metric.metric_type } + + it { is_expected.to eq('rate') } + end + + describe '#to_h' do + it 'returns the metric information' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + + result = metric.to_h + expected = { :common => true, :tags => ['foo:bar'], :type => 'rate', :values => [[1234, 0.1]] } + expect(result).to eq(expected) + end + end +end + +RSpec.describe Datadog::Core::Telemetry::Metric::Gauge do + subject(:metric) { described_class.new('tests', { foo: :bar }) } + let(:generate_metric_type) { 'generate-metrics' } + + describe '.request_type' do + subject(:request_type) { metric.class.request_type } + + it { is_expected.to eq(generate_metric_type) } + end + + describe '#update_value' do + it 'keeps the last value' do + expect(metric.values).to be_nil + metric.update_value(1) + expect(metric.values).to_not be_nil + expect(metric.values[0][1]).to eq 1 + metric.update_value(4) + expect(metric.values[0][1]).to eq 4 + end + + it 'stores the timestamp as part of the values array' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + expect(metric.values[0][0]).to eq 1234 + end + end + + describe '#metric_type' do + subject(:request_type) { metric.metric_type } + + it { is_expected.to eq('gauge') } + end + + describe '#to_h' do + it 'returns the metric information' do + expect(metric).to receive(:timestamp).and_return(1234) + metric.update_value(1) + + result = metric.to_h + expected = { :common => true, :tags => ['foo:bar'], :type => 'gauge', :values => [[1234, 1]] } + expect(result).to eq(expected) + end + end +end + +RSpec.describe Datadog::Core::Telemetry::Metric::Distribution do + subject(:metric) { described_class.new('tests', { foo: :bar }) } + let(:distributions_metric_type) { 'distributions' } + + describe '.request_type' do + subject(:request_type) { metric.class.request_type } + + it { is_expected.to eq(distributions_metric_type) } + end + + describe '#update_value' do + it 'agrregates all values' do + expect(metric.values).to be_nil + metric.update_value(1) + expect(metric.values).to_not be_nil + expect(metric.values).to eq [1] + metric.update_value(4) + expect(metric.values).to eq [1, 4] + end + end + + describe '#metric_type' do + subject(:request_type) { metric.metric_type } + + it { is_expected.to eq('distributions') } + end + + describe '#to_h' do + it 'returns the metric information' do + metric.update_value(1) + + result = metric.to_h + expected = { :common => true, :tags => ['foo:bar'], :type => 'distributions', :values => [1] } + expect(result).to eq(expected) + end + end +end diff --git a/spec/datadog/core/telemetry/metric_worker_spec.rb b/spec/datadog/core/telemetry/metric_worker_spec.rb new file mode 100644 index 00000000000..afca37559c4 --- /dev/null +++ b/spec/datadog/core/telemetry/metric_worker_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +require 'datadog/core/telemetry/metric_worker' + +RSpec.describe Datadog::Core::Telemetry::MetricWorker do + subject(:metric_worker) do + described_class.new(enabled: enabled, heartbeat_interval_seconds: heartbeat_interval_seconds, &block) + end + + let(:enabled) { true } + let(:heartbeat_interval_seconds) { 1.2 } + let(:block) { proc {} } + + after do + metric_worker.stop(true) + metric_worker.join + end + + describe '.new' do + context 'when using default settings' do + subject(:metric_worker) { described_class.new(heartbeat_interval_seconds: heartbeat_interval_seconds, &block) } + it do + is_expected.to have_attributes( + enabled?: true, + loop_base_interval: 1.2, # seconds + task: block + ) + end + end + + context 'when enabled' do + let(:enabled) { true } + + it do + metric_worker + + try_wait_until { metric_worker.running? } + expect(metric_worker).to have_attributes( + run_async?: true, + running?: true, + started?: true + ) + end + end + end +end