Skip to content

Commit 90ea691

Browse files
authored
track telemetry on api requests (#3355)
* start tracking min,max,total bytes seen on stow again and # of instances * add useragent from request header to api request telemetry
1 parent 58c762c commit 90ea691

File tree

8 files changed

+167
-4
lines changed

8 files changed

+167
-4
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
namespace Microsoft.Health.Dicom.Api.Features.Telemetry;
7+
8+
internal static class DicomTelemetry
9+
{
10+
public const string ContextItemPrefix = "Dicom_";
11+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
using EnsureThat;
7+
using Microsoft.ApplicationInsights;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.Health.Dicom.Core.Features.Telemetry;
10+
11+
namespace Microsoft.Health.Dicom.Api.Features.Telemetry;
12+
13+
internal class HttpDicomTelemetryClient : IDicomTelemetryClient
14+
{
15+
private readonly TelemetryClient _telemetryClient;
16+
private readonly IHttpContextAccessor _httpContextAccessor;
17+
18+
public HttpDicomTelemetryClient(TelemetryClient telemetryClient, IHttpContextAccessor httpContextAccessor)
19+
{
20+
_telemetryClient = EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
21+
_httpContextAccessor = EnsureArg.IsNotNull(httpContextAccessor, nameof(httpContextAccessor));
22+
}
23+
24+
public void TrackMetric(string name, int value)
25+
{
26+
// Note: Context Items are prefixed so that the telemetry initializer knows which items to include in the telemetry
27+
_httpContextAccessor.HttpContext.Items[DicomTelemetry.ContextItemPrefix + name] = value;
28+
_telemetryClient.GetMetric(name).TrackValue(value);
29+
}
30+
31+
public void TrackMetric(string name, long value)
32+
{
33+
// Note: Context Items are prefixed so that the telemetry initializer knows which items to include in the telemetry
34+
_httpContextAccessor.HttpContext.Items[DicomTelemetry.ContextItemPrefix + name] = value;
35+
_telemetryClient.GetMetric(name).TrackValue(value);
36+
}
37+
}

src/Microsoft.Health.Dicom.Api/Logging/TelemetryInitializer.cs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// -------------------------------------------------------------------------------------------------
55

6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
69
using EnsureThat;
710
using Microsoft.ApplicationInsights.Channel;
811
using Microsoft.ApplicationInsights.DataContracts;
912
using Microsoft.ApplicationInsights.Extensibility;
1013
using Microsoft.AspNetCore.Http;
1114
using Microsoft.AspNetCore.Mvc.Versioning;
15+
using Microsoft.Extensions.Options;
16+
using Microsoft.Health.Dicom.Api.Features.Telemetry;
17+
using Microsoft.Health.Dicom.Core.Configs;
1218

1319
namespace Microsoft.Health.Dicom.Api.Logging;
1420

@@ -18,19 +24,32 @@ namespace Microsoft.Health.Dicom.Api.Logging;
1824
internal class TelemetryInitializer : ITelemetryInitializer
1925
{
2026
private readonly IHttpContextAccessor _httpContextAccessor;
27+
private readonly bool _enableDataPartitions;
28+
private readonly bool _enableExport;
29+
private readonly bool _enableExternalStore;
2130
private const string ApiVersionColumnName = "ApiVersion";
31+
private const string EnableDataPartitions = "EnableDataPartitions";
32+
private const string EnableExport = "EnableExport";
33+
private const string EnableExternalStore = "EnableExternalStore";
34+
private const string UserAgent = "UserAgent";
2235

23-
public TelemetryInitializer(IHttpContextAccessor httpContextAccessor)
36+
public TelemetryInitializer(IHttpContextAccessor httpContextAccessor, IOptions<FeatureConfiguration> featureConfiguration)
2437
{
2538
_httpContextAccessor = EnsureArg.IsNotNull(httpContextAccessor, nameof(httpContextAccessor));
39+
EnsureArg.IsNotNull(featureConfiguration?.Value, nameof(featureConfiguration));
40+
_enableDataPartitions = featureConfiguration.Value.EnableDataPartitions;
41+
_enableExport = featureConfiguration.Value.EnableExport;
42+
_enableExternalStore = featureConfiguration.Value.EnableExternalStore;
2643
}
2744

2845
public void Initialize(ITelemetry telemetry)
2946
{
30-
AddApiVersionColumn(telemetry);
47+
AddMetadataColumns(telemetry);
48+
if (telemetry is RequestTelemetry requestTelemetry)
49+
AddPropertiesFromHttpContextItems(requestTelemetry);
3150
}
3251

33-
private void AddApiVersionColumn(ITelemetry telemetry)
52+
private void AddMetadataColumns(ITelemetry telemetry)
3453
{
3554
var requestTelemetry = telemetry as RequestTelemetry;
3655
if (requestTelemetry == null)
@@ -52,5 +71,31 @@ private void AddApiVersionColumn(ITelemetry telemetry)
5271
}
5372

5473
requestTelemetry.Properties[ApiVersionColumnName] = version;
74+
requestTelemetry.Properties[EnableDataPartitions] = _enableDataPartitions.ToString();
75+
requestTelemetry.Properties[EnableExport] = _enableExport.ToString();
76+
requestTelemetry.Properties[EnableExternalStore] = _enableExternalStore.ToString();
77+
requestTelemetry.Properties[UserAgent] = _httpContextAccessor.HttpContext?.Request.Headers.UserAgent;
78+
}
79+
80+
private void AddPropertiesFromHttpContextItems(RequestTelemetry requestTelemetry)
81+
{
82+
if (_httpContextAccessor.HttpContext == null)
83+
{
84+
return;
85+
}
86+
87+
IEnumerable<(string Key, string Value)> properties = _httpContextAccessor.HttpContext
88+
.Items
89+
.Select(x => (Key: x.Key.ToString(), x.Value))
90+
.Where(x => x.Key.StartsWith(DicomTelemetry.ContextItemPrefix, StringComparison.Ordinal))
91+
.Select(x => (x.Key[DicomTelemetry.ContextItemPrefix.Length..], x.Value?.ToString()));
92+
93+
foreach ((string key, string value) in properties)
94+
{
95+
if (requestTelemetry.Properties.ContainsKey(key))
96+
requestTelemetry.Properties["DuplicateDimension"] = bool.TrueString;
97+
98+
requestTelemetry.Properties[key] = value;
99+
}
55100
}
56101
}

src/Microsoft.Health.Dicom.Api/Registration/DicomServerServiceCollectionExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
using Microsoft.Health.Dicom.Api.Features.Partitioning;
2828
using Microsoft.Health.Dicom.Api.Features.Routing;
2929
using Microsoft.Health.Dicom.Api.Features.Swagger;
30+
using Microsoft.Health.Dicom.Api.Features.Telemetry;
3031
using Microsoft.Health.Dicom.Api.Logging;
3132
using Microsoft.Health.Dicom.Core.Configs;
3233
using Microsoft.Health.Dicom.Core.Extensions;
3334
using Microsoft.Health.Dicom.Core.Features.Context;
3435
using Microsoft.Health.Dicom.Core.Features.FellowOakDicom;
3536
using Microsoft.Health.Dicom.Core.Features.Routing;
37+
using Microsoft.Health.Dicom.Core.Features.Telemetry;
3638
using Microsoft.Health.Dicom.Core.Registration;
3739
using Microsoft.Health.Encryption.Customer.Configs;
3840
using Microsoft.Health.Encryption.Customer.Extensions;
@@ -174,6 +176,7 @@ public static IDicomServerBuilder AddDicomServer(
174176
services.AddRecyclableMemoryStreamManager(configurationRoot);
175177

176178
services.AddSingleton<ITelemetryInitializer, TelemetryInitializer>();
179+
services.AddSingleton<IDicomTelemetryClient, HttpDicomTelemetryClient>();
177180

178181
CustomDicomImplementation.SetDicomImplementationClassUIDAndVersion();
179182

src/Microsoft.Health.Dicom.Core.UnitTests/Features/Store/DicomStoreServiceTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public class DicomStoreServiceTests
6969
TelemetryChannel = Substitute.For<ITelemetryChannel>(),
7070
});
7171

72+
private readonly IDicomTelemetryClient _dicomTelemetryClient = Substitute.For<IDicomTelemetryClient>();
7273
private readonly StoreService _storeService;
7374
private readonly StoreService _storeServiceDropData;
7475

@@ -94,6 +95,7 @@ public DicomStoreServiceTests()
9495
_storeMeter,
9596
NullLogger<StoreService>.Instance,
9697
Options.Create(new FeatureConfiguration { }),
98+
_dicomTelemetryClient,
9799
_telemetryClient);
98100

99101
_storeServiceDropData = new StoreService(
@@ -104,6 +106,7 @@ public DicomStoreServiceTests()
104106
_storeMeter,
105107
NullLogger<StoreService>.Instance,
106108
Options.Create(new FeatureConfiguration { }),
109+
_dicomTelemetryClient,
107110
_telemetryClient);
108111

109112
DicomValidationBuilderExtension.SkipValidation(null);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
using EnsureThat;
7+
using Microsoft.Health.Dicom.Core.Features.Telemetry;
8+
9+
namespace Microsoft.Health.Dicom.Core.Extensions;
10+
11+
internal static class DicomTelemetryClientExtensions
12+
{
13+
public static void TrackInstanceCount(this IDicomTelemetryClient telemetryClient, int count)
14+
{
15+
EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
16+
telemetryClient.TrackMetric("InstanceCount", count);
17+
}
18+
19+
public static void TrackTotalInstanceBytes(this IDicomTelemetryClient telemetryClient, long bytes)
20+
{
21+
EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
22+
telemetryClient.TrackMetric("TotalInstanceBytes", bytes);
23+
}
24+
25+
public static void TrackMinInstanceBytes(this IDicomTelemetryClient telemetryClient, long bytes)
26+
{
27+
EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
28+
telemetryClient.TrackMetric("MinInstanceBytes", bytes);
29+
}
30+
31+
public static void TrackMaxInstanceBytes(this IDicomTelemetryClient telemetryClient, long bytes)
32+
{
33+
EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
34+
telemetryClient.TrackMetric("MaxInstanceBytes", bytes);
35+
}
36+
}

src/Microsoft.Health.Dicom.Core/Features/Store/StoreService.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class StoreService : IStoreService
6565
private readonly IStoreDatasetValidator _dicomDatasetValidator;
6666
private readonly IStoreOrchestrator _storeOrchestrator;
6767
private readonly IDicomRequestContextAccessor _dicomRequestContextAccessor;
68+
private readonly IDicomTelemetryClient _dicomTelemetryClient;
6869
private readonly TelemetryClient _telemetryClient;
6970
private readonly StoreMeter _storeMeter;
7071
private readonly ILogger _logger;
@@ -80,14 +81,16 @@ public StoreService(
8081
StoreMeter storeMeter,
8182
ILogger<StoreService> logger,
8283
IOptions<FeatureConfiguration> featureConfiguration,
84+
IDicomTelemetryClient dicomTelemetryClient,
8385
TelemetryClient telemetryClient)
8486
{
8587
EnsureArg.IsNotNull(featureConfiguration?.Value, nameof(featureConfiguration));
8688
_storeResponseBuilder = EnsureArg.IsNotNull(storeResponseBuilder, nameof(storeResponseBuilder));
8789
_dicomDatasetValidator = EnsureArg.IsNotNull(dicomDatasetValidator, nameof(dicomDatasetValidator));
8890
_storeOrchestrator = EnsureArg.IsNotNull(storeOrchestrator, nameof(storeOrchestrator));
8991
_dicomRequestContextAccessor = EnsureArg.IsNotNull(dicomRequestContextAccessor, nameof(dicomRequestContextAccessor));
90-
_telemetryClient = EnsureArg.IsNotNull(telemetryClient, nameof(telemetryClient));
92+
_dicomTelemetryClient = EnsureArg.IsNotNull(dicomTelemetryClient, nameof(dicomTelemetryClient));
93+
_telemetryClient = EnsureArg.IsNotNull(telemetryClient, nameof(_telemetryClient));
9194
_storeMeter = EnsureArg.IsNotNull(storeMeter, nameof(storeMeter));
9295
_logger = EnsureArg.IsNotNull(logger, nameof(logger));
9396
}
@@ -104,6 +107,9 @@ public async Task<StoreResponse> ProcessAsync(
104107
_dicomRequestContextAccessor.RequestContext.PartCount = instanceEntries.Count;
105108
_dicomInstanceEntries = instanceEntries;
106109
_requiredStudyInstanceUid = requiredStudyInstanceUid;
110+
_dicomTelemetryClient.TrackInstanceCount(instanceEntries.Count);
111+
112+
long totalLength = 0, minLength = 0, maxLength = 0;
107113

108114
for (int index = 0; index < instanceEntries.Count; index++)
109115
{
@@ -113,13 +119,21 @@ public async Task<StoreResponse> ProcessAsync(
113119
if (length != null)
114120
{
115121
long len = length.GetValueOrDefault();
122+
totalLength += len;
123+
minLength = Math.Min(minLength, len);
124+
maxLength = Math.Max(maxLength, len);
116125
// Update Telemetry
117126
_storeMeter.InstanceLength.Record(len);
118127
_dicomRequestContextAccessor.RequestContext.TotalDicomEgressToStorageBytes += len;
119128
}
120129
}
121130
finally
122131
{
132+
// Update Requests Telemetry
133+
_dicomTelemetryClient.TrackTotalInstanceBytes(totalLength);
134+
_dicomTelemetryClient.TrackMinInstanceBytes(minLength);
135+
_dicomTelemetryClient.TrackMaxInstanceBytes(maxLength);
136+
123137
// Fire and forget.
124138
int capturedIndex = index;
125139

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// -------------------------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// -------------------------------------------------------------------------------------------------
5+
6+
7+
namespace Microsoft.Health.Dicom.Core.Features.Telemetry;
8+
9+
public interface IDicomTelemetryClient
10+
{
11+
void TrackMetric(string name, int value);
12+
13+
void TrackMetric(string name, long value);
14+
}

0 commit comments

Comments
 (0)