Skip to content

Commit 62bb150

Browse files
xuan-cao-swikaylareopellemwear
authored
feat: add basic periodic exporting metric_reader (#1603)
* feat: add basic periodic-reader * feat: lint * feat: make thread properly close * Update metrics_sdk/lib/opentelemetry/sdk/metrics/export/periodic_metric_reader.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update metrics_sdk/lib/opentelemetry/sdk/metrics/export/periodic_metric_reader.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update metrics_sdk/lib/opentelemetry/sdk/metrics/export/periodic_metric_reader.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update metrics_sdk/test/integration/periodic_metric_reader_test.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: periodic reader - revision * feat: change interval and timeout name * feat: change back to millis * revision * lint --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Matthew Wear <matthew.wear@gmail.com>
1 parent 0109aa7 commit 62bb150

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed

metrics_sdk/lib/opentelemetry/sdk/metrics/export.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ module Export
2626
require 'opentelemetry/sdk/metrics/export/metric_reader'
2727
require 'opentelemetry/sdk/metrics/export/in_memory_metric_pull_exporter'
2828
require 'opentelemetry/sdk/metrics/export/console_metric_pull_exporter'
29+
require 'opentelemetry/sdk/metrics/export/periodic_metric_reader'

metrics_sdk/lib/opentelemetry/sdk/metrics/export/in_memory_metric_pull_exporter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def pull
2323
export(collect)
2424
end
2525

26-
def export(metrics)
26+
def export(metrics, timeout: nil)
2727
@mutex.synchronize do
2828
@metric_snapshots << metrics
2929
end
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
module OpenTelemetry
8+
module SDK
9+
module Metrics
10+
module Export
11+
# PeriodicMetricReader provides a minimal example implementation.
12+
class PeriodicMetricReader < MetricReader
13+
# Returns a new instance of the {PeriodicMetricReader}.
14+
#
15+
# @param [Integer] export_interval_millis the maximum interval time.
16+
# Defaults to the value of the OTEL_METRIC_EXPORT_INTERVAL environment
17+
# variable, if set, or 60_000.
18+
# @param [Integer] export_timeout_millis the maximum export timeout.
19+
# Defaults to the value of the OTEL_METRIC_EXPORT_TIMEOUT environment
20+
# variable, if set, or 30_000.
21+
# @param [MetricReader] exporter the (duck type) MetricReader to where the
22+
# recorded metrics are pushed after certain interval.
23+
#
24+
# @return a new instance of the {PeriodicMetricReader}.
25+
def initialize(export_interval_millis: Float(ENV.fetch('OTEL_METRIC_EXPORT_INTERVAL', 60_000)),
26+
export_timeout_millis: Float(ENV.fetch('OTEL_METRIC_EXPORT_TIMEOUT', 30_000)),
27+
exporter: nil)
28+
super()
29+
30+
@export_interval = export_interval_millis / 1000.0
31+
@export_timeout = export_timeout_millis / 1000.0
32+
@exporter = exporter
33+
@thread = nil
34+
@continue = false
35+
@mutex = Mutex.new
36+
@export_mutex = Mutex.new
37+
38+
start
39+
end
40+
41+
def shutdown(timeout: nil)
42+
thread = lock do
43+
@continue = false # force termination in next iteration
44+
@thread
45+
end
46+
thread&.join(@export_interval)
47+
@exporter.force_flush if @exporter.respond_to?(:force_flush)
48+
@exporter.shutdown
49+
Export::SUCCESS
50+
rescue StandardError => e
51+
OpenTelemetry.handle_error(exception: e, message: 'Fail to shutdown PeriodicMetricReader.')
52+
Export::FAILURE
53+
end
54+
55+
def force_flush(timeout: nil)
56+
export(timeout: timeout)
57+
Export::SUCCESS
58+
rescue StandardError
59+
Export::FAILURE
60+
end
61+
62+
private
63+
64+
def start
65+
@continue = true
66+
if @exporter.nil?
67+
OpenTelemetry.logger.warn 'Missing exporter in PeriodicMetricReader.'
68+
elsif @thread&.alive?
69+
OpenTelemetry.logger.warn 'PeriodicMetricReader is still running. Please shutdown it if it needs to restart.'
70+
else
71+
@thread = Thread.new do
72+
while @continue
73+
sleep(@export_interval)
74+
begin
75+
Timeout.timeout(@export_timeout) do
76+
export(timeout: @export_timeout)
77+
end
78+
rescue Timeout::Error => e
79+
OpenTelemetry.handle_error(exception: e, message: 'PeriodicMetricReader timeout.')
80+
end
81+
end
82+
end
83+
end
84+
end
85+
86+
def export(timeout: nil)
87+
@export_mutex.synchronize do
88+
collected_metrics = collect
89+
@exporter.export(collected_metrics, timeout: timeout || @export_timeout) unless collected_metrics.empty?
90+
end
91+
end
92+
93+
def lock(&block)
94+
@mutex.synchronize(&block)
95+
end
96+
end
97+
end
98+
end
99+
end
100+
end
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
require 'test_helper'
8+
9+
describe OpenTelemetry::SDK do
10+
describe '#periodic_metric_reader' do
11+
before { reset_metrics_sdk }
12+
13+
it 'emits 2 metrics after 10 seconds' do
14+
OpenTelemetry::SDK.configure
15+
16+
metric_exporter = OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new
17+
periodic_metric_reader = OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(export_interval_millis: 5000, export_timeout_millis: 5000, exporter: metric_exporter)
18+
19+
OpenTelemetry.meter_provider.add_metric_reader(periodic_metric_reader)
20+
21+
meter = OpenTelemetry.meter_provider.meter('test')
22+
counter = meter.create_counter('counter', unit: 'smidgen', description: 'a small amount of something')
23+
24+
counter.add(1)
25+
counter.add(2, attributes: { 'a' => 'b' })
26+
counter.add(2, attributes: { 'a' => 'b' })
27+
counter.add(3, attributes: { 'b' => 'c' })
28+
counter.add(4, attributes: { 'd' => 'e' })
29+
30+
sleep(8)
31+
32+
periodic_metric_reader.shutdown
33+
snapshot = metric_exporter.metric_snapshots
34+
35+
_(snapshot.size).must_equal(2)
36+
37+
first_snapshot = snapshot[0]
38+
_(first_snapshot[0].name).must_equal('counter')
39+
_(first_snapshot[0].unit).must_equal('smidgen')
40+
_(first_snapshot[0].description).must_equal('a small amount of something')
41+
42+
_(first_snapshot[0].instrumentation_scope.name).must_equal('test')
43+
44+
_(first_snapshot[0].data_points[0].value).must_equal(1)
45+
_(first_snapshot[0].data_points[0].attributes).must_equal({})
46+
47+
_(first_snapshot[0].data_points[1].value).must_equal(4)
48+
_(first_snapshot[0].data_points[1].attributes).must_equal('a' => 'b')
49+
50+
_(first_snapshot[0].data_points[2].value).must_equal(3)
51+
_(first_snapshot[0].data_points[2].attributes).must_equal('b' => 'c')
52+
53+
_(first_snapshot[0].data_points[3].value).must_equal(4)
54+
_(first_snapshot[0].data_points[3].attributes).must_equal('d' => 'e')
55+
56+
_(periodic_metric_reader.instance_variable_get(:@thread).alive?).must_equal false
57+
end
58+
59+
it 'emits 1 metric after 1 second when interval is > 1 second' do
60+
OpenTelemetry::SDK.configure
61+
62+
metric_exporter = OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new
63+
periodic_metric_reader = OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(export_interval_millis: 5000, export_timeout_millis: 5000, exporter: metric_exporter)
64+
65+
OpenTelemetry.meter_provider.add_metric_reader(periodic_metric_reader)
66+
67+
meter = OpenTelemetry.meter_provider.meter('test')
68+
counter = meter.create_counter('counter', unit: 'smidgen', description: 'a small amount of something')
69+
70+
counter.add(1)
71+
counter.add(2, attributes: { 'a' => 'b' })
72+
counter.add(2, attributes: { 'a' => 'b' })
73+
counter.add(3, attributes: { 'b' => 'c' })
74+
counter.add(4, attributes: { 'd' => 'e' })
75+
76+
sleep(1)
77+
78+
periodic_metric_reader.shutdown
79+
snapshot = metric_exporter.metric_snapshots
80+
81+
_(snapshot.size).must_equal(1)
82+
_(periodic_metric_reader.instance_variable_get(:@thread).alive?).must_equal false
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)