Skip to content

Commit cef94bf

Browse files
authored
SQL connection creation duration tracking (#9086)
* Methods for tracking Sql connection creation duration. * Reporting new metric * Reporting milliseconds for create sql connection duration. * Better check for all event names. Testing both sync and async SQL creation duration metric
1 parent 1cb2166 commit cef94bf

9 files changed

Lines changed: 109 additions & 20 deletions

File tree

src/GitHubVulnerabilities2Db/Gallery/ThrowingTelemetryService.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,5 +390,15 @@ public void TrackVulnerabilitiesCacheRefreshDuration(TimeSpan duration)
390390
{
391391
throw new NotImplementedException();
392392
}
393+
394+
public IDisposable TrackSyncSqlConnectionCreationDuration()
395+
{
396+
throw new NotImplementedException();
397+
}
398+
399+
public IDisposable TrackAsyncSqlConnectionCreationDuration()
400+
{
401+
throw new NotImplementedException();
402+
}
393403
}
394404
}

src/NuGetGallery.Services/NuGetGallery.Services.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
<Compile Include="SupportRequest\Models\ISupportRequestDbContext.cs" />
243243
<Compile Include="SupportRequest\Models\SupportRequestDbContext.cs" />
244244
<Compile Include="SupportRequest\SupportRequestService.cs" />
245+
<Compile Include="Telemetry\DurationMetric.cs" />
245246
<Compile Include="Telemetry\ITelemetryClient.cs" />
246247
<Compile Include="Telemetry\ITelemetryService.cs" />
247248
<Compile Include="Telemetry\Obfuscator.cs" />
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
7+
namespace NuGetGallery
8+
{
9+
public class DurationTracker : IDisposable
10+
{
11+
private readonly Stopwatch _timer;
12+
13+
private Action<TimeSpan> _trackAction;
14+
15+
public DurationTracker(
16+
Action<TimeSpan> trackAction)
17+
{
18+
_trackAction = trackAction ?? throw new ArgumentNullException(nameof(trackAction));
19+
_timer = Stopwatch.StartNew();
20+
}
21+
22+
public void Dispose() => _trackAction(_timer.Elapsed);
23+
}
24+
}

src/NuGetGallery.Services/Telemetry/ITelemetryService.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,5 +424,8 @@ void TrackABTestEvaluated(
424424
/// </summary>
425425
/// <param name="endpoint"></param>
426426
void TrackApiRequest(string endpoint);
427+
428+
IDisposable TrackSyncSqlConnectionCreationDuration();
429+
IDisposable TrackAsyncSqlConnectionCreationDuration();
427430
}
428431
}

src/NuGetGallery.Services/Telemetry/TelemetryService.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace NuGetGallery
1919
{
2020
public class TelemetryService : ITelemetryService, IFeatureFlagTelemetryService
2121
{
22-
public class Events
22+
public static class Events
2323
{
2424
public const string ODataQueryFilter = "ODataQueryFilter";
2525
public const string ODataCustomQuery = "ODataCustomQuery";
@@ -95,6 +95,7 @@ public class Events
9595
public const string VulnerabilitiesCacheRefreshDurationMs = "VulnerabilitiesCacheRefreshDurationMs";
9696
public const string InstanceUptime = "InstanceUptimeInDays";
9797
public const string ApiRequest = "ApiRequest";
98+
public const string CreateSqlConnectionDurationMs = "CreateSqlConnectionDurationMs";
9899
}
99100

100101
private readonly IDiagnosticsSource _diagnosticsSource;
@@ -236,6 +237,10 @@ public class Events
236237

237238
public const string Endpoint = "Endpoint";
238239

240+
public const string Kind = "Kind";
241+
public const string Sync = "Sync";
242+
public const string Async = "Async";
243+
239244
public TelemetryService(IDiagnosticsSource diagnosticsSource, ITelemetryClient telemetryClient)
240245
{
241246
_telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient));
@@ -1138,6 +1143,18 @@ public void TrackApiRequest(string endpoint)
11381143
_telemetryClient.TrackAggregatedMetric(Events.ApiRequest, 1, Endpoint, endpoint);
11391144
}
11401145

1146+
public IDisposable TrackSyncSqlConnectionCreationDuration()
1147+
=> TrackSqlConnectionCreationDuration(Sync);
1148+
1149+
public IDisposable TrackAsyncSqlConnectionCreationDuration()
1150+
=> TrackSqlConnectionCreationDuration(Async);
1151+
1152+
private IDisposable TrackSqlConnectionCreationDuration(string kind)
1153+
{
1154+
return new DurationTracker(duration =>
1155+
_telemetryClient.TrackAggregatedMetric(Events.CreateSqlConnectionDurationMs, duration.TotalMilliseconds, Kind, kind));
1156+
}
1157+
11411158
/// <summary>
11421159
/// We use <see cref="ITelemetryClient.TrackMetric(string, double, IDictionary{string, string})"/> instead of
11431160
/// <see cref="ITelemetryClient.TrackEvent(string, IDictionary{string, string}, IDictionary{string, double})"/>

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ protected override void Load(ContainerBuilder builder)
192192
.As<ISqlConnectionFactory>()
193193
.SingleInstance();
194194

195-
builder.Register(c => new EntitiesContext(CreateDbConnection(galleryDbConnectionFactory), configuration.Current.ReadOnlyMode))
195+
builder.Register(c => new EntitiesContext(CreateDbConnection(galleryDbConnectionFactory, telemetryService), configuration.Current.ReadOnlyMode))
196196
.AsSelf()
197197
.As<IEntitiesContext>()
198198
.As<DbContext>()
@@ -278,15 +278,15 @@ protected override void Load(ContainerBuilder builder)
278278
.As<IEntityRepository<PackageRename>>()
279279
.InstancePerLifetimeScope();
280280

281-
ConfigureGalleryReadOnlyReplicaEntitiesContext(builder, loggerFactory, configuration, secretInjector);
281+
ConfigureGalleryReadOnlyReplicaEntitiesContext(builder, loggerFactory, configuration, secretInjector, telemetryService);
282282

283283
var supportDbConnectionFactory = CreateDbConnectionFactory(
284284
loggerFactory,
285285
nameof(SupportRequestDbContext),
286286
configuration.Current.SqlConnectionStringSupportRequest,
287287
secretInjector);
288288

289-
builder.Register(c => new SupportRequestDbContext(CreateDbConnection(supportDbConnectionFactory)))
289+
builder.Register(c => new SupportRequestDbContext(CreateDbConnection(supportDbConnectionFactory, telemetryService)))
290290
.AsSelf()
291291
.As<ISupportRequestDbContext>()
292292
.InstancePerLifetimeScope();
@@ -503,7 +503,7 @@ protected override void Load(ContainerBuilder builder)
503503
break;
504504
}
505505

506-
RegisterAsynchronousValidation(builder, loggerFactory, configuration, secretInjector);
506+
RegisterAsynchronousValidation(builder, loggerFactory, configuration, secretInjector, telemetryService);
507507

508508
RegisterAuditingServices(builder, configuration.Current.StorageType);
509509

@@ -963,28 +963,35 @@ private static ISqlConnectionFactory CreateDbConnectionFactory(
963963
return new AzureSqlConnectionFactory(connectionString, secretInjector, logger);
964964
}
965965

966-
public static DbConnection CreateDbConnection(ISqlConnectionFactory connectionFactory)
966+
public static DbConnection CreateDbConnection(ISqlConnectionFactory connectionFactory, ITelemetryService telemetryService)
967967
{
968-
if (connectionFactory.TryCreate(out var connection))
968+
using (telemetryService.TrackSyncSqlConnectionCreationDuration())
969969
{
970-
return connection;
970+
if (connectionFactory.TryCreate(out var connection))
971+
{
972+
return connection;
973+
}
974+
}
975+
using (telemetryService.TrackAsyncSqlConnectionCreationDuration())
976+
{
977+
return Task.Run(() => connectionFactory.CreateAsync()).Result;
971978
}
972-
return Task.Run(() => connectionFactory.CreateAsync()).Result;
973979
}
974980

975981
private static void ConfigureGalleryReadOnlyReplicaEntitiesContext(
976982
ContainerBuilder builder,
977983
ILoggerFactory loggerFactory,
978984
ConfigurationService configuration,
979-
ICachingSecretInjector secretInjector)
985+
ICachingSecretInjector secretInjector,
986+
ITelemetryService telemetryService)
980987
{
981988
var galleryDbReadOnlyReplicaConnectionFactory = CreateDbConnectionFactory(
982989
loggerFactory,
983990
nameof(ReadOnlyEntitiesContext),
984991
configuration.Current.SqlReadOnlyReplicaConnectionString ?? configuration.Current.SqlConnectionString,
985992
secretInjector);
986993

987-
builder.Register(c => new ReadOnlyEntitiesContext(CreateDbConnection(galleryDbReadOnlyReplicaConnectionFactory)))
994+
builder.Register(c => new ReadOnlyEntitiesContext(CreateDbConnection(galleryDbReadOnlyReplicaConnectionFactory, telemetryService)))
988995
.As<IReadOnlyEntitiesContext>()
989996
.InstancePerLifetimeScope();
990997

@@ -997,15 +1004,16 @@ private static void ConfigureValidationEntitiesContext(
9971004
ContainerBuilder builder,
9981005
ILoggerFactory loggerFactory,
9991006
ConfigurationService configuration,
1000-
ICachingSecretInjector secretInjector)
1007+
ICachingSecretInjector secretInjector,
1008+
ITelemetryService telemetryService)
10011009
{
10021010
var validationDbConnectionFactory = CreateDbConnectionFactory(
10031011
loggerFactory,
10041012
nameof(ValidationEntitiesContext),
10051013
configuration.Current.SqlConnectionStringValidation,
10061014
secretInjector);
10071015

1008-
builder.Register(c => new ValidationEntitiesContext(CreateDbConnection(validationDbConnectionFactory)))
1016+
builder.Register(c => new ValidationEntitiesContext(CreateDbConnection(validationDbConnectionFactory, telemetryService)))
10091017
.AsSelf()
10101018
.InstancePerLifetimeScope();
10111019

@@ -1026,7 +1034,8 @@ private void RegisterAsynchronousValidation(
10261034
ContainerBuilder builder,
10271035
ILoggerFactory loggerFactory,
10281036
ConfigurationService configuration,
1029-
ICachingSecretInjector secretInjector)
1037+
ICachingSecretInjector secretInjector,
1038+
ITelemetryService telemetryService)
10301039
{
10311040
builder
10321041
.RegisterType<NuGet.Services.Validation.ServiceBusMessageSerializer>()
@@ -1052,7 +1061,7 @@ private void RegisterAsynchronousValidation(
10521061

10531062
if (configuration.Current.AsynchronousPackageValidationEnabled)
10541063
{
1055-
ConfigureValidationEntitiesContext(builder, loggerFactory, configuration, secretInjector);
1064+
ConfigureValidationEntitiesContext(builder, loggerFactory, configuration, secretInjector, telemetryService);
10561065

10571066
builder
10581067
.Register(c =>

src/VerifyMicrosoftPackage/Fakes/FakeTelemetryService.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,5 +392,15 @@ public void TrackVulnerabilitiesCacheRefreshDuration(TimeSpan duration)
392392
{
393393
throw new NotImplementedException();
394394
}
395+
396+
public IDisposable TrackSyncSqlConnectionCreationDuration()
397+
{
398+
throw new NotImplementedException();
399+
}
400+
401+
public IDisposable TrackAsyncSqlConnectionCreationDuration()
402+
{
403+
throw new NotImplementedException();
404+
}
395405
}
396406
}

tests/NuGetGallery.Facts/App_Start/DefaultDependenciesModuleFacts.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public void TriesSyncBeforeAsync()
204204
.Setup(cf => cf.TryCreate(out It.Ref<SqlConnection>.IsAny))
205205
.Returns(true);
206206

207-
DefaultDependenciesModule.CreateDbConnection(connectionFactoryMock.Object);
207+
DefaultDependenciesModule.CreateDbConnection(connectionFactoryMock.Object, Mock.Of<ITelemetryService>());
208208
connectionFactoryMock
209209
.Verify(cf => cf.TryCreate(out It.Ref<SqlConnection>.IsAny), Times.Once);
210210
connectionFactoryMock
@@ -222,7 +222,7 @@ public void FallsBackToAsyncIfSyncFails()
222222
.Setup(cf => cf.CreateAsync())
223223
.ReturnsAsync((SqlConnection)null);
224224

225-
DefaultDependenciesModule.CreateDbConnection(connectionFactoryMock.Object);
225+
DefaultDependenciesModule.CreateDbConnection(connectionFactoryMock.Object, Mock.Of<ITelemetryService>());
226226
connectionFactoryMock
227227
.Verify(cf => cf.TryCreate(out It.Ref<SqlConnection>.IsAny), Times.Once);
228228
connectionFactoryMock

tests/NuGetGallery.Facts/Services/TelemetryServiceFacts.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,31 @@ public static IEnumerable<object[]> TrackMetricNames_Data
360360
(TrackAction)(s => s.TrackApiRequest("SomeEndpoint")),
361361
true
362362
};
363+
364+
yield return new object[] { "CreateSqlConnectionDurationMs",
365+
(TrackAction)(s => s.TrackSyncSqlConnectionCreationDuration().Dispose()),
366+
true
367+
};
368+
369+
yield return new object[] { "CreateSqlConnectionDurationMs",
370+
(TrackAction)(s => s.TrackAsyncSqlConnectionCreationDuration().Dispose()),
371+
true
372+
};
363373
}
364374
}
365375

366376
[Fact]
367377
public void TrackEventNamesIncludesAllEvents()
368378
{
369-
var expectedCount = typeof(TelemetryService.Events).GetFields().Length;
370-
var actualCount = TrackMetricNames_Data.Count();
379+
var eventNames = typeof(TelemetryService.Events)
380+
.GetFields()
381+
.Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string))
382+
.Select(f => (string)f.GetValue(null))
383+
.ToList();
384+
385+
var testedNames = new HashSet<string>(TrackMetricNames_Data.Select(element => (string)element[0]));
371386

372-
Assert.Equal(expectedCount, actualCount);
387+
Assert.All(eventNames, name => testedNames.Contains(name));
373388
}
374389

375390
[Theory]

0 commit comments

Comments
 (0)