Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record stats for serialization time #1628

Merged
merged 2 commits into from
Apr 17, 2024

Conversation

plantfansam
Copy link
Contributor

@plantfansam plantfansam commented Apr 15, 2024

Serialization takes place during export and is a resource-intensive process; users may want to see how long it takes.

Remnants of earlier approach

I had to prove to myself that the value returned from a block passed to a block-handling function will be returned from the block-handling function even when there's an ensure in that function (I was worried that something from the ensure block would be returned). For documentation, I'll reproduce that experiment here:

Given the Ruby

def mymethod
  "return from mymethod"
end

def wrap_mymethod
  begin
    yield
  ensure
    puts "Ensure ran within wrap_mymethod"
  end
end


ret = wrap_mymethod { mymethod }

puts "Return value from `wrap_mymethod`: #{ret}"

Yields output:

ruby yield.rb
Ensure ran within wrap_mymethod
Return value from `wrap_mymethod`: return from mymethod

@@ -382,6 +382,17 @@
_(result).must_equal(SUCCESS)
end

it 'records metrics' do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of mocking the metrics reporter, we could move InMemoryMetricsReporter from https://github.com/open-telemetry/opentelemetry-ruby/blob/2bcb4643b144300b0e18cba6ae61e93eb75bc5b0/exporter/zipkin/test/test_helper.rb#L43-L42 to opentelemetry-test-helpers if reviewers would like.

@@ -77,7 +77,9 @@ def initialize(endpoint: nil,
def export(span_data, timeout: nil)
return FAILURE if @shutdown

send_bytes(encode(span_data), timeout: timeout)
send_bytes(
measure_encode_duration { encode(span_data) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should inline measure_encode_duration into the encode method. We didn't do that with measure_request_duration because the block it wraps is a stdlib method (@http.request(request)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing

)
end
measure_encode_duration do
Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just an indentation change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was unclear in my earlier comment. I don't think we need measure_encode_duration. We just need to ... measure the duration of the encode method. I.e.

        def encode(span_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
          start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
          Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode(
            Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new(
              resource_spans: span_data
                .group_by(&:resource)
                .map do |resource, span_datas|
                  Opentelemetry::Proto::Trace::V1::ResourceSpans.new(
                    resource: Opentelemetry::Proto::Resource::V1::Resource.new(
                      attributes: resource.attribute_enumerator.map { |key, value| as_otlp_key_value(key, value) }
                    ),
                    scope_spans: span_datas
                      .group_by(&:instrumentation_scope)
                      .map do |il, sds|
                        Opentelemetry::Proto::Trace::V1::ScopeSpans.new(
                          scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new(
                            name: il.name,
                            version: il.version
                          ),
                          spans: sds.map { |sd| as_otlp_span(sd) }
                        )
                      end
                  )
                end
              )
            )
        rescue StandardError => e
          OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#encode')
          nil
        ensure
          stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
          duration_ms = 1000.0 * (stop - start)
          @metrics_reporter.record_value('otel.otlp_exporter.encode_duration',
                                         value: duration_ms)
        end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this makes more sense; I was on autopilot. Will make change.

@plantfansam plantfansam force-pushed the serialization-time branch 2 times, most recently from 220baee to e80c46d Compare April 17, 2024 13:58
ensure
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
duration_ms = 1000.0 * (stop - start)
@metrics_reporter.record_value('otel.otlp_exporter.encode_duration',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would y'all feel about adding a helper that accepts a block:

@metrics_reporter.record_duration(name, labels: labels) do
   # your code we want to measure goes here
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this one more time, it's worth a refactor. I am not super keen on the block-based approach if all our callsites are full methods. I think I'd prefer:

def foo
  probe = start_probe('otel.otlp_exporter.foo')
  ...
ensure
  probe.finish
end

over:

def foo
  @metrics_reporter.record_duration('otel.otlp_exporter.foo') do
    ...
  end
end

simply because it avoids deeper indentation as we add instrumentation to existing methods. The latter is better for measurements of small chunks of code within a larger method.

@fbogsany fbogsany merged commit 6bae4e1 into open-telemetry:main Apr 17, 2024
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants