Skip to content

Commit

Permalink
Create metrics.Factory adapter for OTEL Metrics (#5661)
Browse files Browse the repository at this point in the history
**Which problem is this PR solving?**

This PR addresses a part of the issue [#5633
](#5633)

**Description of the changes**
This is a Draft PR to bridge the OTEL Metrics instead of using Internal
Metrics to minimize code changes.
**How was this change tested?**

The changes were tested by running the following command:

```bash
make test
```

**Checklist**

- [x] I have read
[CONTRIBUTING_GUIDELINES.md](https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md)
- [x] I have signed all commits
- [x] I have added unit tests for the new functionality
- [x] I have run lint and test steps successfully
  - `for jaeger: make lint test`
  - `for jaeger-ui: yarn lint` and `yarn test`

---------

Signed-off-by: Wise-Wizard <saransh.shankar@gmail.com>
Signed-off-by: Saransh Shankar <103821431+Wise-Wizard@users.noreply.github.com>
Signed-off-by: Yuri Shkuro <github@ysh.us>
Co-authored-by: Yuri Shkuro <yurishkuro@users.noreply.github.com>
Co-authored-by: Yuri Shkuro <github@ysh.us>
  • Loading branch information
3 people committed Jun 30, 2024
1 parent afb4c87 commit 301dbec
Show file tree
Hide file tree
Showing 7 changed files with 583 additions and 0 deletions.
113 changes: 113 additions & 0 deletions internal/metrics/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) 2024 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package benchmark_test

import (
"testing"

"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
promExporter "go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/sdk/metric"

"github.com/jaegertracing/jaeger/internal/metrics/otelmetrics"
prom "github.com/jaegertracing/jaeger/internal/metrics/prometheus"
"github.com/jaegertracing/jaeger/pkg/metrics"
"github.com/jaegertracing/jaeger/pkg/testutils"
)

func TestMain(m *testing.M) {
testutils.VerifyGoLeaks(m)
}

func setupPrometheusFactory() metrics.Factory {
reg := prometheus.NewRegistry()
return prom.New(prom.WithRegisterer(reg))
}

func setupOTELFactory(b *testing.B) metrics.Factory {
registry := prometheus.NewRegistry()
exporter, err := promExporter.New(promExporter.WithRegisterer(registry))
require.NoError(b, err)
meterProvider := metric.NewMeterProvider(
metric.WithReader(exporter),
)
return otelmetrics.NewFactory(meterProvider)
}

func benchmarkCounter(b *testing.B, factory metrics.Factory) {
counter := factory.Counter(metrics.Options{
Name: "test_counter",
Tags: map[string]string{"tag1": "value1"},
})

for i := 0; i < b.N; i++ {
counter.Inc(1)
}
}

func benchmarkGauge(b *testing.B, factory metrics.Factory) {
gauge := factory.Gauge(metrics.Options{
Name: "test_gauge",
Tags: map[string]string{"tag1": "value1"},
})

for i := 0; i < b.N; i++ {
gauge.Update(1)
}
}

func benchmarkTimer(b *testing.B, factory metrics.Factory) {
timer := factory.Timer(metrics.TimerOptions{
Name: "test_timer",
Tags: map[string]string{"tag1": "value1"},
})

for i := 0; i < b.N; i++ {
timer.Record(100)
}
}

func benchmarkHistogram(b *testing.B, factory metrics.Factory) {
histogram := factory.Histogram(metrics.HistogramOptions{
Name: "test_histogram",
Tags: map[string]string{"tag1": "value1"},
})

for i := 0; i < b.N; i++ {
histogram.Record(1.0)
}
}

func BenchmarkPrometheusCounter(b *testing.B) {
benchmarkCounter(b, setupPrometheusFactory())
}

func BenchmarkOTELCounter(b *testing.B) {
benchmarkCounter(b, setupOTELFactory(b))
}

func BenchmarkPrometheusGauge(b *testing.B) {
benchmarkGauge(b, setupPrometheusFactory())
}

func BenchmarkOTELGauge(b *testing.B) {
benchmarkGauge(b, setupOTELFactory(b))
}

func BenchmarkPrometheusTimer(b *testing.B) {
benchmarkTimer(b, setupPrometheusFactory())
}

func BenchmarkOTELTimer(b *testing.B) {
benchmarkTimer(b, setupOTELFactory(b))
}

func BenchmarkPrometheusHistogram(b *testing.B) {
benchmarkHistogram(b, setupPrometheusFactory())
}

func BenchmarkOTELHistogram(b *testing.B) {
benchmarkHistogram(b, setupOTELFactory(b))
}
20 changes: 20 additions & 0 deletions internal/metrics/otelmetrics/counter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2024 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package otelmetrics

import (
"context"

"go.opentelemetry.io/otel/metric"
)

type otelCounter struct {
counter metric.Int64Counter
fixedCtx context.Context
option metric.AddOption
}

func (c *otelCounter) Inc(value int64) {
c.counter.Add(c.fixedCtx, value, c.option)
}
133 changes: 133 additions & 0 deletions internal/metrics/otelmetrics/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) 2024 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package otelmetrics

import (
"context"
"log"
"strings"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"

"github.com/jaegertracing/jaeger/pkg/metrics"
)

type otelFactory struct {
meter metric.Meter
scope string
separator string
normalizer *strings.Replacer
tags map[string]string
}

func NewFactory(meterProvider metric.MeterProvider) metrics.Factory {
return &otelFactory{
meter: meterProvider.Meter("jaeger-v2"),
separator: ".",
normalizer: strings.NewReplacer(" ", "_", ".", "_", "-", "_"),
tags: make(map[string]string),
}
}

func (f *otelFactory) Counter(opts metrics.Options) metrics.Counter {
name := f.subScope(opts.Name)
counter, err := f.meter.Int64Counter(name)
if err != nil {
log.Printf("Error creating OTEL counter: %v", err)
return metrics.NullCounter
}
return &otelCounter{
counter: counter,
fixedCtx: context.Background(),
option: attributeSetOption(f.mergeTags(opts.Tags)),
}
}

func (f *otelFactory) Gauge(opts metrics.Options) metrics.Gauge {
name := f.subScope(opts.Name)
gauge, err := f.meter.Int64Gauge(name)
if err != nil {
log.Printf("Error creating OTEL gauge: %v", err)
return metrics.NullGauge
}

return &otelGauge{
gauge: gauge,
fixedCtx: context.Background(),
option: attributeSetOption(f.mergeTags(opts.Tags)),
}
}

func (f *otelFactory) Histogram(opts metrics.HistogramOptions) metrics.Histogram {
name := f.subScope(opts.Name)
histogram, err := f.meter.Float64Histogram(name)
if err != nil {
log.Printf("Error creating OTEL histogram: %v", err)
return metrics.NullHistogram
}

return &otelHistogram{
histogram: histogram,
fixedCtx: context.Background(),
option: attributeSetOption(f.mergeTags(opts.Tags)),
}
}

func (f *otelFactory) Timer(opts metrics.TimerOptions) metrics.Timer {
name := f.subScope(opts.Name)
timer, err := f.meter.Float64Histogram(name, metric.WithUnit("s"))
if err != nil {
log.Printf("Error creating OTEL timer: %v", err)
return metrics.NullTimer
}
return &otelTimer{
histogram: timer,
fixedCtx: context.Background(),
option: attributeSetOption(f.mergeTags(opts.Tags)),
}
}

func (f *otelFactory) Namespace(opts metrics.NSOptions) metrics.Factory {
return &otelFactory{
meter: f.meter,
scope: f.subScope(opts.Name),
separator: f.separator,
normalizer: f.normalizer,
tags: f.mergeTags(opts.Tags),
}
}

func (f *otelFactory) subScope(name string) string {
if f.scope == "" {
return f.normalize(name)
}
if name == "" {
return f.normalize(f.scope)
}
return f.normalize(f.scope + f.separator + name)
}

func (f *otelFactory) normalize(v string) string {
return f.normalizer.Replace(v)
}

func (f *otelFactory) mergeTags(tags map[string]string) map[string]string {
merged := make(map[string]string)
for k, v := range f.tags {
merged[k] = v
}
for k, v := range tags {
merged[k] = v
}
return merged
}

func attributeSetOption(tags map[string]string) metric.MeasurementOption {
attributes := make([]attribute.KeyValue, 0, len(tags))
for k, v := range tags {
attributes = append(attributes, attribute.String(k, v))
}
return metric.WithAttributes(attributes...)
}
Loading

0 comments on commit 301dbec

Please sign in to comment.