...

Source file src/go.opentelemetry.io/otel/internal/global/meter_test.go

Documentation: go.opentelemetry.io/otel/internal/global

     1  // Copyright The OpenTelemetry Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package global // import "go.opentelemetry.io/otel/internal/global"
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sync"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"go.opentelemetry.io/otel/metric"
    27  	"go.opentelemetry.io/otel/metric/noop"
    28  )
    29  
    30  func TestMeterProviderConcurrentSafe(t *testing.T) {
    31  	mp := &meterProvider{}
    32  	done := make(chan struct{})
    33  	finish := make(chan struct{})
    34  	go func() {
    35  		defer close(done)
    36  		for i := 0; ; i++ {
    37  			mp.Meter(fmt.Sprintf("a%d", i))
    38  			select {
    39  			case <-finish:
    40  				return
    41  			default:
    42  			}
    43  		}
    44  	}()
    45  
    46  	mp.setDelegate(noop.NewMeterProvider())
    47  	close(finish)
    48  	<-done
    49  }
    50  
    51  var zeroCallback metric.Callback = func(ctx context.Context, or metric.Observer) error {
    52  	return nil
    53  }
    54  
    55  func TestMeterConcurrentSafe(t *testing.T) {
    56  	mtr := &meter{}
    57  
    58  	wg := &sync.WaitGroup{}
    59  	wg.Add(1)
    60  	done := make(chan struct{})
    61  	finish := make(chan struct{})
    62  	go func() {
    63  		defer close(done)
    64  		for i, once := 0, false; ; i++ {
    65  			name := fmt.Sprintf("a%d", i)
    66  			_, _ = mtr.Float64ObservableCounter(name)
    67  			_, _ = mtr.Float64ObservableUpDownCounter(name)
    68  			_, _ = mtr.Float64ObservableGauge(name)
    69  			_, _ = mtr.Int64ObservableCounter(name)
    70  			_, _ = mtr.Int64ObservableUpDownCounter(name)
    71  			_, _ = mtr.Int64ObservableGauge(name)
    72  			_, _ = mtr.Float64Counter(name)
    73  			_, _ = mtr.Float64UpDownCounter(name)
    74  			_, _ = mtr.Float64Histogram(name)
    75  			_, _ = mtr.Int64Counter(name)
    76  			_, _ = mtr.Int64UpDownCounter(name)
    77  			_, _ = mtr.Int64Histogram(name)
    78  			_, _ = mtr.RegisterCallback(zeroCallback)
    79  			if !once {
    80  				wg.Done()
    81  				once = true
    82  			}
    83  			select {
    84  			case <-finish:
    85  				return
    86  			default:
    87  			}
    88  		}
    89  	}()
    90  
    91  	wg.Wait()
    92  	mtr.setDelegate(noop.NewMeterProvider())
    93  	close(finish)
    94  	<-done
    95  }
    96  
    97  func TestUnregisterConcurrentSafe(t *testing.T) {
    98  	mtr := &meter{}
    99  	reg, err := mtr.RegisterCallback(zeroCallback)
   100  	require.NoError(t, err)
   101  
   102  	wg := &sync.WaitGroup{}
   103  	wg.Add(1)
   104  	done := make(chan struct{})
   105  	finish := make(chan struct{})
   106  	go func() {
   107  		defer close(done)
   108  		for i, once := 0, false; ; i++ {
   109  			_ = reg.Unregister()
   110  			if !once {
   111  				wg.Done()
   112  				once = true
   113  			}
   114  			select {
   115  			case <-finish:
   116  				return
   117  			default:
   118  			}
   119  		}
   120  	}()
   121  	_ = reg.Unregister()
   122  
   123  	wg.Wait()
   124  	mtr.setDelegate(noop.NewMeterProvider())
   125  	close(finish)
   126  	<-done
   127  }
   128  
   129  func testSetupAllInstrumentTypes(t *testing.T, m metric.Meter) (metric.Float64Counter, metric.Float64ObservableCounter) {
   130  	afcounter, err := m.Float64ObservableCounter("test_Async_Counter")
   131  	require.NoError(t, err)
   132  	_, err = m.Float64ObservableUpDownCounter("test_Async_UpDownCounter")
   133  	assert.NoError(t, err)
   134  	_, err = m.Float64ObservableGauge("test_Async_Gauge")
   135  	assert.NoError(t, err)
   136  
   137  	_, err = m.Int64ObservableCounter("test_Async_Counter")
   138  	assert.NoError(t, err)
   139  	_, err = m.Int64ObservableUpDownCounter("test_Async_UpDownCounter")
   140  	assert.NoError(t, err)
   141  	_, err = m.Int64ObservableGauge("test_Async_Gauge")
   142  	assert.NoError(t, err)
   143  
   144  	_, err = m.RegisterCallback(func(ctx context.Context, obs metric.Observer) error {
   145  		obs.ObserveFloat64(afcounter, 3)
   146  		return nil
   147  	}, afcounter)
   148  	require.NoError(t, err)
   149  
   150  	sfcounter, err := m.Float64Counter("test_Async_Counter")
   151  	require.NoError(t, err)
   152  	_, err = m.Float64UpDownCounter("test_Async_UpDownCounter")
   153  	assert.NoError(t, err)
   154  	_, err = m.Float64Histogram("test_Async_Histogram")
   155  	assert.NoError(t, err)
   156  
   157  	_, err = m.Int64Counter("test_Async_Counter")
   158  	assert.NoError(t, err)
   159  	_, err = m.Int64UpDownCounter("test_Async_UpDownCounter")
   160  	assert.NoError(t, err)
   161  	_, err = m.Int64Histogram("test_Async_Histogram")
   162  	assert.NoError(t, err)
   163  
   164  	return sfcounter, afcounter
   165  }
   166  
   167  // This is to emulate a read from an exporter.
   168  func testCollect(t *testing.T, m metric.Meter) {
   169  	if tMeter, ok := m.(*meter); ok {
   170  		m, ok = tMeter.delegate.Load().(metric.Meter)
   171  		if !ok {
   172  			t.Error("meter was not delegated")
   173  			return
   174  		}
   175  	}
   176  	tMeter, ok := m.(*testMeter)
   177  	if !ok {
   178  		t.Error("collect called on non-test Meter")
   179  		return
   180  	}
   181  	tMeter.collect()
   182  }
   183  
   184  func TestMeterProviderDelegatesCalls(t *testing.T) {
   185  	// The global MeterProvider should directly call the underlying MeterProvider
   186  	// if it is set prior to Meter() being called.
   187  
   188  	// globalMeterProvider := otel.GetMeterProvider
   189  	globalMeterProvider := &meterProvider{}
   190  
   191  	mp := &testMeterProvider{}
   192  
   193  	// otel.SetMeterProvider(mp)
   194  	globalMeterProvider.setDelegate(mp)
   195  
   196  	assert.Equal(t, 0, mp.count)
   197  
   198  	meter := globalMeterProvider.Meter("go.opentelemetry.io/otel/metric/internal/global/meter_test")
   199  
   200  	ctr, actr := testSetupAllInstrumentTypes(t, meter)
   201  
   202  	ctr.Add(context.Background(), 5)
   203  
   204  	testCollect(t, meter) // This is a hacky way to emulate a read from an exporter
   205  
   206  	// Calls to Meter() after setDelegate() should be executed by the delegate
   207  	require.IsType(t, &testMeter{}, meter)
   208  	tMeter := meter.(*testMeter)
   209  	assert.Equal(t, 1, tMeter.afCount)
   210  	assert.Equal(t, 1, tMeter.afUDCount)
   211  	assert.Equal(t, 1, tMeter.afGauge)
   212  	assert.Equal(t, 1, tMeter.aiCount)
   213  	assert.Equal(t, 1, tMeter.aiUDCount)
   214  	assert.Equal(t, 1, tMeter.aiGauge)
   215  	assert.Equal(t, 1, tMeter.sfCount)
   216  	assert.Equal(t, 1, tMeter.sfUDCount)
   217  	assert.Equal(t, 1, tMeter.sfHist)
   218  	assert.Equal(t, 1, tMeter.siCount)
   219  	assert.Equal(t, 1, tMeter.siUDCount)
   220  	assert.Equal(t, 1, tMeter.siHist)
   221  	assert.Equal(t, 1, len(tMeter.callbacks))
   222  
   223  	// Because the Meter was provided by testmeterProvider it should also return our test instrument
   224  	require.IsType(t, &testCountingFloatInstrument{}, ctr, "the meter did not delegate calls to the meter")
   225  	assert.Equal(t, 1, ctr.(*testCountingFloatInstrument).count)
   226  
   227  	require.IsType(t, &testCountingFloatInstrument{}, actr, "the meter did not delegate calls to the meter")
   228  	assert.Equal(t, 1, actr.(*testCountingFloatInstrument).count)
   229  
   230  	assert.Equal(t, 1, mp.count)
   231  }
   232  
   233  func TestMeterDelegatesCalls(t *testing.T) {
   234  	// The global MeterProvider should directly provide a Meter instance that
   235  	// can be updated.  If the SetMeterProvider is called after a Meter was
   236  	// obtained, but before instruments only the instrument should be generated
   237  	// by the delegated type.
   238  
   239  	globalMeterProvider := &meterProvider{}
   240  
   241  	mp := &testMeterProvider{}
   242  
   243  	assert.Equal(t, 0, mp.count)
   244  
   245  	m := globalMeterProvider.Meter("go.opentelemetry.io/otel/metric/internal/global/meter_test")
   246  
   247  	globalMeterProvider.setDelegate(mp)
   248  
   249  	ctr, actr := testSetupAllInstrumentTypes(t, m)
   250  
   251  	ctr.Add(context.Background(), 5)
   252  
   253  	testCollect(t, m) // This is a hacky way to emulate a read from an exporter
   254  
   255  	// Calls to Meter methods after setDelegate() should be executed by the delegate
   256  	require.IsType(t, &meter{}, m)
   257  	tMeter := m.(*meter).delegate.Load().(*testMeter)
   258  	require.NotNil(t, tMeter)
   259  	assert.Equal(t, 1, tMeter.afCount)
   260  	assert.Equal(t, 1, tMeter.afUDCount)
   261  	assert.Equal(t, 1, tMeter.afGauge)
   262  	assert.Equal(t, 1, tMeter.aiCount)
   263  	assert.Equal(t, 1, tMeter.aiUDCount)
   264  	assert.Equal(t, 1, tMeter.aiGauge)
   265  	assert.Equal(t, 1, tMeter.sfCount)
   266  	assert.Equal(t, 1, tMeter.sfUDCount)
   267  	assert.Equal(t, 1, tMeter.sfHist)
   268  	assert.Equal(t, 1, tMeter.siCount)
   269  	assert.Equal(t, 1, tMeter.siUDCount)
   270  	assert.Equal(t, 1, tMeter.siHist)
   271  
   272  	// Because the Meter was provided by testmeterProvider it should also return our test instrument
   273  	require.IsType(t, &testCountingFloatInstrument{}, ctr, "the meter did not delegate calls to the meter")
   274  	assert.Equal(t, 1, ctr.(*testCountingFloatInstrument).count)
   275  
   276  	// Because the Meter was provided by testmeterProvider it should also return our test instrument
   277  	require.IsType(t, &testCountingFloatInstrument{}, actr, "the meter did not delegate calls to the meter")
   278  	assert.Equal(t, 1, actr.(*testCountingFloatInstrument).count)
   279  
   280  	assert.Equal(t, 1, mp.count)
   281  }
   282  
   283  func TestMeterDefersDelegations(t *testing.T) {
   284  	// If SetMeterProvider is called after instruments are registered, the
   285  	// instruments should be recreated with the new meter.
   286  
   287  	// globalMeterProvider := otel.GetMeterProvider
   288  	globalMeterProvider := &meterProvider{}
   289  
   290  	m := globalMeterProvider.Meter("go.opentelemetry.io/otel/metric/internal/global/meter_test")
   291  
   292  	ctr, actr := testSetupAllInstrumentTypes(t, m)
   293  
   294  	ctr.Add(context.Background(), 5)
   295  
   296  	mp := &testMeterProvider{}
   297  
   298  	// otel.SetMeterProvider(mp)
   299  	globalMeterProvider.setDelegate(mp)
   300  
   301  	testCollect(t, m) // This is a hacky way to emulate a read from an exporter
   302  
   303  	// Calls to Meter() before setDelegate() should be the delegated type
   304  	require.IsType(t, &meter{}, m)
   305  	tMeter := m.(*meter).delegate.Load().(*testMeter)
   306  	require.NotNil(t, tMeter)
   307  	assert.Equal(t, 1, tMeter.afCount)
   308  	assert.Equal(t, 1, tMeter.afUDCount)
   309  	assert.Equal(t, 1, tMeter.afGauge)
   310  	assert.Equal(t, 1, tMeter.aiCount)
   311  	assert.Equal(t, 1, tMeter.aiUDCount)
   312  	assert.Equal(t, 1, tMeter.aiGauge)
   313  	assert.Equal(t, 1, tMeter.sfCount)
   314  	assert.Equal(t, 1, tMeter.sfUDCount)
   315  	assert.Equal(t, 1, tMeter.sfHist)
   316  	assert.Equal(t, 1, tMeter.siCount)
   317  	assert.Equal(t, 1, tMeter.siUDCount)
   318  	assert.Equal(t, 1, tMeter.siHist)
   319  
   320  	// Because the Meter was a delegate it should return a delegated instrument
   321  
   322  	assert.IsType(t, &sfCounter{}, ctr)
   323  	assert.IsType(t, &afCounter{}, actr)
   324  	assert.Equal(t, 1, mp.count)
   325  }
   326  
   327  func TestRegistrationDelegation(t *testing.T) {
   328  	// globalMeterProvider := otel.GetMeterProvider
   329  	globalMeterProvider := &meterProvider{}
   330  
   331  	m := globalMeterProvider.Meter("go.opentelemetry.io/otel/metric/internal/global/meter_test")
   332  	require.IsType(t, &meter{}, m)
   333  	mImpl := m.(*meter)
   334  
   335  	actr, err := m.Float64ObservableCounter("test_Async_Counter")
   336  	require.NoError(t, err)
   337  
   338  	var called0 bool
   339  	reg0, err := m.RegisterCallback(func(context.Context, metric.Observer) error {
   340  		called0 = true
   341  		return nil
   342  	}, actr)
   343  	require.NoError(t, err)
   344  	require.Equal(t, 1, mImpl.registry.Len(), "callback not registered")
   345  	// This means reg0 should not be delegated.
   346  	assert.NoError(t, reg0.Unregister())
   347  	assert.Equal(t, 0, mImpl.registry.Len(), "callback not unregistered")
   348  
   349  	var called1 bool
   350  	reg1, err := m.RegisterCallback(func(context.Context, metric.Observer) error {
   351  		called1 = true
   352  		return nil
   353  	}, actr)
   354  	require.NoError(t, err)
   355  	require.Equal(t, 1, mImpl.registry.Len(), "second callback not registered")
   356  
   357  	mp := &testMeterProvider{}
   358  
   359  	// otel.SetMeterProvider(mp)
   360  	globalMeterProvider.setDelegate(mp)
   361  
   362  	testCollect(t, m) // This is a hacky way to emulate a read from an exporter
   363  	require.False(t, called0, "pre-delegation unregistered callback called")
   364  	require.True(t, called1, "callback not called")
   365  
   366  	called1 = false
   367  	assert.NoError(t, reg1.Unregister(), "unregister second callback")
   368  
   369  	testCollect(t, m) // This is a hacky way to emulate a read from an exporter
   370  	assert.False(t, called1, "unregistered callback called")
   371  
   372  	assert.NotPanics(t, func() {
   373  		assert.NoError(t, reg1.Unregister(), "duplicate unregister calls")
   374  	})
   375  }
   376  

View as plain text