Skip to content

Commit

Permalink
Add OpenTelemetry library and expose metrics for Witsml requests (#2291)
Browse files Browse the repository at this point in the history
  • Loading branch information
lindsve authored Apr 9, 2024
1 parent 3659117 commit 50c22e6
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 17 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
<PackageVersion Include="System.ServiceModel.Http" Version="8.0.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageVersion Include="LiteDB" Version="5.0.19" />
<PackageVersion Include="xunit" Version="2.7.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7">
Expand Down
11 changes: 11 additions & 0 deletions Src/Witsml/Metrics/WitsmlMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Witsml.Metrics;

public enum WitsmlMethod
{
GetFromStore,
AddToStore,
UpdateInStore,
DeleteFromStore,
GetCap,
GetVersion
}
74 changes: 74 additions & 0 deletions Src/Witsml/Metrics/WitsmlMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Reflection;
using System.Threading.Tasks;

using Serilog;

using Witsml.ServiceReference;

namespace Witsml.Metrics;

internal sealed class WitsmlMetrics
{
#region Singleton
// We still want to collect unified metrics in case there are multiple WitsmlClient instances used

private static readonly Lazy<WitsmlMetrics> LazyInstance =
new(() => new WitsmlMetrics());

public static WitsmlMetrics Instance { get { return LazyInstance.Value; } }

private WitsmlMetrics() { }

#endregion

private static readonly AssemblyName AssemblyName =
typeof(WitsmlMetrics).Assembly.GetName();

internal static readonly string MeterName = AssemblyName.Name;

private static readonly Meter MeterInstance = new(MeterName, AssemblyName.Version!.ToString());

private readonly Histogram<long> _requestDuration = MeterInstance.CreateHistogram<long>(
"witsml.requests.duration",
unit: "s",
description: "Time spent during requests to a Witsml server");

private readonly UpDownCounter<int> _activeRequests =
MeterInstance.CreateUpDownCounter<int>(
"witsml.requests.active",
description: "Number of active requests");

internal async Task<TResponseType> MeasureQuery<TResponseType>(Uri serverUri, WitsmlMethod method, string witsmlType, Task<TResponseType> wmlsTask)
where TResponseType : IWitsmlResponse
{
var tagList = new TagList
{
{ "host", serverUri.Host },
{ "method", Enum.GetName(method) },
{ "objectType", witsmlType },
};

Stopwatch timer = null;
TResponseType response;
try
{
_activeRequests.Add(1, tagList);
timer = Stopwatch.StartNew();
response = await wmlsTask;
}
finally
{
timer?.Stop();
_activeRequests.Add(-1, tagList);
}

tagList.Add("resultCode", response.GetResultCode());

var elapsedSeconds = timer.ElapsedMilliseconds / 1000;
_requestDuration.Record(elapsedSeconds, tagList);
return response;
}
}
17 changes: 17 additions & 0 deletions Src/Witsml/Metrics/WitsmlMetricsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using OpenTelemetry.Metrics;

namespace Witsml.Metrics;

public static class WitsmlMetricsExtensions
{
// ReSharper disable once UnusedMember.Global (For usage by external applications)
public static MeterProviderBuilder AddWitsmlInstrumentation(this MeterProviderBuilder builder)
{
builder.AddMeter(WitsmlMetrics.MeterName);
builder.AddView("witsml.requests.duration", new ExplicitBucketHistogramConfiguration()
{
Boundaries = [0.5, 1, 3, 5, 10, 15, 30, 60]
});
return builder;
}
}
38 changes: 35 additions & 3 deletions Src/Witsml/ServiceReference/WitsmlServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable InconsistentNaming
namespace Witsml.ServiceReference
{
// ReSharper disable once ClassNeverInstantiated.Global
// ReSharper disable once InconsistentNaming
public partial class WMLS_GetFromStoreResponse
internal interface IWitsmlResponse
{
public string GetResultCode();
}

public partial class WMLS_GetFromStoreResponse : IWitsmlResponse
{
public string GetResultCode() => Result.ToString();

public override string ToString()
{
return $"Result: {Result}\nXMLout: {XMLout}\nSuppMsgOut: {SuppMsgOut}";
}
}

public partial class WMLS_AddToStoreResponse : IWitsmlResponse
{
public string GetResultCode() => Result.ToString();
}

public partial class WMLS_UpdateInStoreResponse : IWitsmlResponse
{
public string GetResultCode() => Result.ToString();
}

public partial class WMLS_DeleteFromStoreResponse : IWitsmlResponse
{
public string GetResultCode() => Result.ToString();
}

public partial class WMLS_GetCapResponse : IWitsmlResponse
{
public string GetResultCode() => Result.ToString();
}

public partial class WMLS_GetVersionResponse : IWitsmlResponse
{
public string GetResultCode() => Result;
}
}
1 change: 1 addition & 0 deletions Src/Witsml/Witsml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="System.ServiceModel.Http" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
</ItemGroup>
</Project>
79 changes: 65 additions & 14 deletions Src/Witsml/WitsmlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

using Witsml.Data;
using Witsml.Extensions;
using Witsml.Metrics;
using Witsml.ServiceReference;
using Witsml.Xml;

Expand Down Expand Up @@ -37,6 +38,7 @@ public class WitsmlClient : WitsmlClientBase, IWitsmlClient
private readonly StoreSoapPortClient _client;
private readonly Uri _serverUrl;
private IQueryLogger _queryLogger;
private readonly WitsmlMetrics _witsmlMetrics;

[Obsolete("Use the WitsmlClientOptions based constructor instead")]
public WitsmlClient(string hostname, string username, string password, WitsmlClientCapabilities clientCapabilities, TimeSpan? requestTimeout = null,
Expand Down Expand Up @@ -65,6 +67,7 @@ public WitsmlClient(Action<WitsmlClientOptions> options)

_client = CreateSoapClient(witsmlClientOptions);

_witsmlMetrics = WitsmlMetrics.Instance;
SetupQueryLogging(witsmlClientOptions.LogQueries);
}

Expand Down Expand Up @@ -132,7 +135,11 @@ private async Task<T> GetFromStoreInnerAsync<T>(T query, OptionsIn optionsIn) wh
CapabilitiesIn = _clientCapabilities
};

WMLS_GetFromStoreResponse response = await _client.WMLS_GetFromStoreAsync(request);
WMLS_GetFromStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.GetFromStore,
query.TypeName,
_client.WMLS_GetFromStoreAsync(request));

LogQueriesSentAndReceived(nameof(_client.WMLS_GetFromStoreAsync), this._serverUrl, query, optionsIn, request.QueryIn,
response.IsSuccessful(), response.XMLout, response.Result, response.SuppMsgOut);
Expand Down Expand Up @@ -164,7 +171,11 @@ private async Task<T> GetFromStoreInnerAsync<T>(T query, OptionsIn optionsIn) wh
CapabilitiesIn = _clientCapabilities
};

WMLS_GetFromStoreResponse response = await _client.WMLS_GetFromStoreAsync(request);
WMLS_GetFromStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.GetFromStore,
query.TypeName,
_client.WMLS_GetFromStoreAsync(request));

LogQueriesSentAndReceived(nameof(_client.WMLS_GetFromStoreAsync), this._serverUrl, query, optionsIn,
request.QueryIn, response.IsSuccessful(), response.XMLout, response.Result, response.SuppMsgOut);
Expand All @@ -182,7 +193,7 @@ private async Task<T> GetFromStoreInnerAsync<T>(T query, OptionsIn optionsIn) wh
}
}

private string GetQueryType(string query)
private static string GetQueryType(string query)
{
XmlReaderSettings settings = new()
{
Expand Down Expand Up @@ -213,7 +224,12 @@ public async Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn)
CapabilitiesIn = _clientCapabilities
};

WMLS_GetFromStoreResponse response = await _client.WMLS_GetFromStoreAsync(request);
WMLS_GetFromStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.GetFromStore,
type,
_client.WMLS_GetFromStoreAsync(request));

LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_GetFromStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), response.XMLout, response.Result, response.SuppMsgOut);

Expand All @@ -237,7 +253,11 @@ public async Task<QueryResult> AddToStoreAsync<T>(T query) where T : IWitsmlQuer
CapabilitiesIn = _clientCapabilities
};

WMLS_AddToStoreResponse response = await _client.WMLS_AddToStoreAsync(request);
WMLS_AddToStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.AddToStore,
query.TypeName,
_client.WMLS_AddToStoreAsync(request));

LogQueriesSentAndReceived(nameof(_client.WMLS_AddToStoreAsync), this._serverUrl, query, optionsIn,
request.XMLin, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);
Expand Down Expand Up @@ -268,7 +288,12 @@ public async Task<string> AddToStoreAsync(string query, OptionsIn optionsIn = nu
CapabilitiesIn = _clientCapabilities
};

WMLS_AddToStoreResponse response = await _client.WMLS_AddToStoreAsync(request);
WMLS_AddToStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.AddToStore,
type,
_client.WMLS_AddToStoreAsync(request));

LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_AddToStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

Expand All @@ -291,7 +316,11 @@ public async Task<QueryResult> UpdateInStoreAsync<T>(T query) where T : IWitsmlQ
CapabilitiesIn = _clientCapabilities
};

WMLS_UpdateInStoreResponse response = await _client.WMLS_UpdateInStoreAsync(request);
WMLS_UpdateInStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.UpdateInStore,
query.TypeName,
_client.WMLS_UpdateInStoreAsync(request));

LogQueriesSentAndReceived(nameof(_client.WMLS_UpdateInStoreAsync), this._serverUrl, query, null,
request.XMLin, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);
Expand Down Expand Up @@ -322,7 +351,12 @@ public async Task<string> UpdateInStoreAsync(string query, OptionsIn optionsIn =
CapabilitiesIn = _clientCapabilities
};

WMLS_UpdateInStoreResponse response = await _client.WMLS_UpdateInStoreAsync(request);
WMLS_UpdateInStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.UpdateInStore,
type,
_client.WMLS_UpdateInStoreAsync(request));

LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_UpdateInStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

Expand All @@ -345,7 +379,11 @@ public async Task<QueryResult> DeleteFromStoreAsync<T>(T query) where T : IWitsm
CapabilitiesIn = _clientCapabilities
};

WMLS_DeleteFromStoreResponse response = await _client.WMLS_DeleteFromStoreAsync(request);
WMLS_DeleteFromStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.DeleteFromStore,
query.TypeName,
_client.WMLS_DeleteFromStoreAsync(request));

LogQueriesSentAndReceived(nameof(_client.WMLS_DeleteFromStoreAsync), this._serverUrl, query, null,
request.QueryIn, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);
Expand Down Expand Up @@ -376,7 +414,12 @@ public async Task<string> DeleteFromStoreAsync(string query, OptionsIn optionsIn
CapabilitiesIn = _clientCapabilities
};

WMLS_DeleteFromStoreResponse response = await _client.WMLS_DeleteFromStoreAsync(request);
WMLS_DeleteFromStoreResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.DeleteFromStore,
type,
_client.WMLS_DeleteFromStoreAsync(request));

LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_DeleteFromStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

Expand All @@ -389,11 +432,15 @@ public async Task<string> DeleteFromStoreAsync(string query, OptionsIn optionsIn

public async Task<QueryResult> TestConnectionAsync()
{
WMLS_GetVersionResponse response = await _client.WMLS_GetVersionAsync();
WMLS_GetVersionResponse response =
await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.GetVersion,
"",
_client.WMLS_GetVersionAsync());

if (string.IsNullOrEmpty(response.Result))
{
throw new Exception("Error while testing connection: Server failed to return a valid version");
}

// Spec requires a comma-seperated list of supported versions without spaces
var versions = response.Result.Split(CommonConstants.DataSeparator);
Expand All @@ -405,7 +452,11 @@ public async Task<QueryResult> TestConnectionAsync()

public async Task<WitsmlCapServers> GetCap()
{
WMLS_GetCapResponse response = await _client.WMLS_GetCapAsync(new WMLS_GetCapRequest("dataVersion=1.4.1.1"));
WMLS_GetCapResponse response = await _witsmlMetrics.MeasureQuery(
_serverUrl,
WitsmlMethod.GetCap,
"",
_client.WMLS_GetCapAsync(new WMLS_GetCapRequest("dataVersion=1.4.1.1")));

if (response.IsSuccessful())
return XmlHelper.Deserialize(response.CapabilitiesOut, new WitsmlCapServers());
Expand Down

0 comments on commit 50c22e6

Please sign in to comment.