Add ProxyMeter to MeterFactoryBase which invokes real Meter if provided. (#631)

* Add ProxyMeter to MeterFactoryBase which invokes real Meter if provided.

* remove unwanted copy of a test file.

Co-authored-by: Sergey Kanzhelev <S.Kanzhelev@live.com>
This commit is contained in:
Cijo Thomas 2020-04-27 16:26:10 -07:00 committed by GitHub
parent e56ee5d46b
commit f333287509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 62 deletions

View File

@ -13,14 +13,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Metrics
{
/// <summary>
/// Creates Meters for an instrumentation library.
/// Libraries should use this class as follows to obtain Meter instance.
/// MeterFactoryBase.Default.GetMeter("libraryname", "version").
/// </summary>
public class MeterFactoryBase
{
private static NoOpMeter noOpMeter = new NoOpMeter();
private static ProxyMeter proxyMeter = new ProxyMeter();
private static bool isInitialized;
private static MeterFactoryBase defaultFactory = new MeterFactoryBase();
/// <summary>
@ -32,14 +37,50 @@ namespace OpenTelemetry.Metrics
}
/// <summary>
/// Returns an IMeter for a given name and version.
/// Sets the default instance of <see cref="MeterFactoryBase"/>.
/// </summary>
/// <param name="meterFactory">Instance of <see cref="MeterFactoryBase"/>.</param>
/// <remarks>
/// This method can only be called once. Calling it multiple times will throw an <see cref="System.InvalidOperationException"/>.
/// </remarks>
/// <exception cref="System.InvalidOperationException">Thrown when called multiple times.</exception>
public static void SetDefault(MeterFactoryBase meterFactory)
{
if (isInitialized)
{
throw new InvalidOperationException("Default factory is already set");
}
defaultFactory = meterFactory ?? throw new ArgumentNullException(nameof(meterFactory));
// some libraries might have already used and cached ProxyMeter.
// let's update it to real one and forward all calls.
// resource assignment is not possible for libraries that cache tracer before SDK is initialized.
// SDK (Tracer) must be at least partially initialized before any collection starts to capture resources.
// we might be able to work this around with events.
proxyMeter.UpdateMeter(defaultFactory.GetMeter(null));
isInitialized = true;
}
/// <summary>
/// Returns a Meter for a given name and version.
/// </summary>
/// <param name="name">Name of the instrumentation library.</param>
/// <param name="version">Version of the instrumentation library (optional).</param>
/// <returns>Meter with the given component name and version.</returns>
/// <returns>Meter for the given name and version information.</returns>
public virtual Meter GetMeter(string name, string version = null)
{
return noOpMeter;
return isInitialized ? defaultFactory.GetMeter(name, version) : proxyMeter;
}
// for tests
internal void Reset()
{
proxyMeter = new ProxyMeter();
isInitialized = false;
defaultFactory = new MeterFactoryBase();
}
}
}

View File

@ -1,4 +1,4 @@
// <copyright file="NoOpMeter.cs" company="OpenTelemetry Authors">
// <copyright file="ProxyMeter.cs" company="OpenTelemetry Authors">
// Copyright 2018, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -15,49 +15,66 @@
// </copyright>
using System;
using System.Collections.Generic;
using System.Threading;
namespace OpenTelemetry.Metrics
{
internal sealed class NoOpMeter : Meter
/// <summary>
/// Proxy Meter which act as a No-Op Meter, until real meter is provided.
/// </summary>
internal sealed class ProxyMeter : Meter
{
public NoOpMeter()
private Meter realMeter;
public ProxyMeter()
{
}
public override CounterMetric<double> CreateDoubleCounter(string name, bool monotonic = true)
{
return NoOpCounterMetric<double>.Instance;
return this.realMeter != null ? this.realMeter.CreateDoubleCounter(name, monotonic) : NoOpCounterMetric<double>.Instance;
}
public override MeasureMetric<double> CreateDoubleMeasure(string name, bool absolute = true)
{
return NoOpMeasureMetric<double>.Instance;
return this.realMeter != null ? this.realMeter.CreateDoubleMeasure(name, absolute) : NoOpMeasureMetric<double>.Instance;
}
public override DoubleObserverMetric CreateDoubleObserver(string name, Action<DoubleObserverMetric> callback, bool absolute = true)
{
return NoOpDoubleObserverMetric.Instance;
return this.realMeter != null ? this.realMeter.CreateDoubleObserver(name, callback, absolute) : NoOpDoubleObserverMetric.Instance;
}
public override CounterMetric<long> CreateInt64Counter(string name, bool monotonic = true)
{
return NoOpCounterMetric<long>.Instance;
return this.realMeter != null ? this.realMeter.CreateInt64Counter(name, monotonic) : NoOpCounterMetric<long>.Instance;
}
public override MeasureMetric<long> CreateInt64Measure(string name, bool absolute = true)
{
return NoOpMeasureMetric<long>.Instance;
return this.realMeter != null ? this.realMeter.CreateInt64Measure(name, absolute) : NoOpMeasureMetric<long>.Instance;
}
public override Int64ObserverMetric CreateInt64Observer(string name, Action<Int64ObserverMetric> callback, bool absolute = true)
{
return NoOpInt64ObserverMetric.Instance;
return this.realMeter != null ? this.realMeter.CreateInt64Observer(name, callback, absolute) : NoOpInt64ObserverMetric.Instance;
}
public override LabelSet GetLabelSet(IEnumerable<KeyValuePair<string, string>> labels)
{
// return no op
return LabelSet.BlankLabelSet;
return this.realMeter != null ? this.realMeter.GetLabelSet(labels) : LabelSet.BlankLabelSet;
}
public void UpdateMeter(Meter realMeter)
{
if (this.realMeter != null)
{
return;
}
// just in case user calls init concurrently
Interlocked.CompareExchange(ref this.realMeter, realMeter, null);
}
}
}

View File

@ -16,7 +16,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using OpenTelemetry.Metrics.Export;

View File

@ -0,0 +1,92 @@
// <copyright file="MeterFactoryBaseTests.cs" company="OpenTelemetry Authors">
// Copyright 2018, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using OpenTelemetry.Metrics.Configuration;
using Xunit;
namespace OpenTelemetry.Metrics.Config.Test
{
public class MeterFactoryBaseTests : IDisposable
{
public MeterFactoryBaseTests()
{
MeterFactoryBase.Default.Reset();
}
[Fact]
public void MeterFactory_Default()
{
Assert.NotNull(MeterFactoryBase.Default);
var defaultMeter = MeterFactoryBase.Default.GetMeter("");
Assert.NotNull(defaultMeter);
Assert.Same(defaultMeter, MeterFactoryBase.Default.GetMeter("named meter"));
var counter = defaultMeter.CreateDoubleCounter("ctr");
Assert.IsType<NoOpCounterMetric<double>>(counter);
}
[Fact]
public void MeterFactory_SetDefault()
{
var factory = MeterFactory.Create(b => { });
MeterFactoryBase.SetDefault(factory);
var defaultMeter = MeterFactoryBase.Default.GetMeter("");
Assert.NotNull(defaultMeter);
Assert.IsType<MeterSdk>(defaultMeter);
Assert.NotSame(defaultMeter, MeterFactoryBase.Default.GetMeter("named meter"));
var counter = defaultMeter.CreateDoubleCounter("ctr");
Assert.IsType<DoubleCounterMetricSdk>(counter);
}
[Fact]
public void MeterFactory_SetDefaultNull()
{
Assert.Throws<ArgumentNullException>(() => MeterFactoryBase.SetDefault(null));
}
[Fact]
public void MeterFactory_SetDefaultTwice_Throws()
{
MeterFactoryBase.SetDefault(MeterFactory.Create(b => { }));
Assert.Throws<InvalidOperationException>(() => MeterFactoryBase.SetDefault(MeterFactory.Create(b => { })));
}
[Fact]
public void MeterFactory_UpdateDefault_CachedTracer()
{
var defaultMeter = MeterFactoryBase.Default.GetMeter("");
var noOpCounter = defaultMeter.CreateDoubleCounter("ctr");
Assert.IsType<NoOpCounterMetric<double>>(noOpCounter);
MeterFactoryBase.SetDefault(MeterFactory.Create(b => { }));
var counter = defaultMeter.CreateDoubleCounter("ctr");
Assert.IsType<DoubleCounterMetricSdk>(counter);
var newdefaultMeter = MeterFactoryBase.Default.GetMeter("");
Assert.NotSame(defaultMeter, newdefaultMeter);
Assert.IsType<MeterSdk>(newdefaultMeter);
}
public void Dispose()
{
MeterFactoryBase.Default.Reset();
}
}
}

View File

@ -1,47 +0,0 @@
// <copyright file="MeterFactoryBaseTests.cs" company="OpenTelemetry Authors">
// Copyright 2018, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using OpenTelemetry.Metrics;
using Xunit;
namespace OpenTelemetry.Tests.Impl.Trace.Config
{
public class MeterFactoryBaseTests
{
[Fact]
public void MeterFactoryBase_Default()
{
Assert.NotNull(MeterFactoryBase.Default);
var defaultMeter = MeterFactoryBase.Default.GetMeter("");
Assert.NotNull(defaultMeter);
Assert.IsType<NoOpMeter>(defaultMeter);
var namedMeter = MeterFactoryBase.Default.GetMeter("named meter");
// The same NoOpMeter must be returned always.
Assert.Same(defaultMeter, namedMeter);
var counter = defaultMeter.CreateDoubleCounter("somename");
Assert.IsType<NoOpCounterMetric<double>>(counter);
var labels1 = new List<KeyValuePair<string, string>>();
labels1.Add(new KeyValuePair<string, string>("dim1", "value1"));
var counterBoundIntrument = counter.Bind(labels1);
Assert.IsType<NoOpBoundCounterMetric<double>>(counterBoundIntrument);
}
}
}