Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

track total content length and files indexed #3318

Merged
merged 41 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0e3140c
add index on content length to prep for use with getting totals
esmadau Jan 22, 2024
bbb4c10
fix name
esmadau Jan 22, 2024
fe0431c
add function to get sum and total indexed
esmadau Jan 25, 2024
c887257
move frequency to options
esmadau Jan 26, 2024
831bf7b
only run if idp
esmadau Jan 26, 2024
9e1d480
make readonly struct
esmadau Jan 26, 2024
1c98fd0
move to ctor
esmadau Jan 26, 2024
05dceec
add unique name to meter to avoid collisions
esmadau Jan 26, 2024
d0c90d5
fix nits
esmadau Jan 27, 2024
0ddb0a5
rename method
esmadau Jan 27, 2024
902def8
other fixes
esmadau Jan 27, 2024
bb85d7d
rename namespace
esmadau Jan 27, 2024
cfbf899
rename namespace
esmadau Jan 27, 2024
42d7f6f
move frequency to every midnight
esmadau Jan 27, 2024
55cc0e3
Merge branch 'main' into users/esmadau/clindex
esmadau Feb 1, 2024
a3a29d4
rename sproc to not have a version
esmadau Feb 1, 2024
8107357
rename column returned
esmadau Feb 1, 2024
ebd558b
update min version
esmadau Feb 1, 2024
c6884d8
use named columns for safety
esmadau Feb 1, 2024
58ee244
make indexedFileProps equitable
esmadau Feb 1, 2024
ebd44f3
have section name reflect specific collection implemented here
esmadau Feb 1, 2024
881d032
emit success/fail dimension on collection exception
esmadau Feb 1, 2024
3e08d35
nit fix
esmadau Feb 1, 2024
2b5496e
assert on emitting a failed metric
esmadau Feb 1, 2024
f971151
extract getting metric points and tags into a common tests helper
esmadau Feb 1, 2024
0e30abc
added cleanup step for tests
esmadau Feb 1, 2024
a9e38b2
use single line expressions
esmadau Feb 2, 2024
10ad43f
fix nits
esmadau Feb 2, 2024
118a143
move helper methods into telemetry namespace in core and expose as ex…
esmadau Feb 2, 2024
fa12503
fix nits
esmadau Feb 2, 2024
de3fbf0
fix nit
esmadau Feb 2, 2024
d406cb6
rename sproc
esmadau Feb 2, 2024
8fc641d
use count_big
esmadau Feb 2, 2024
2e86a18
nit fix
esmadau Feb 2, 2024
1b5b2da
remove unnecessary counters
esmadau Feb 2, 2024
1f23fe3
rm unnecessary meter
esmadau Feb 2, 2024
cc13a4a
use readrow
esmadau Feb 2, 2024
93a4b80
nit
esmadau Feb 2, 2024
05ecd58
fix nits
esmadau Feb 2, 2024
3ab76c7
happy times
esmadau Feb 3, 2024
1a15ed2
Merge branch 'main' into users/esmadau/clindex
esmadau Feb 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;

namespace Microsoft.Health.Dicom.Core.Features.Common;

/// <summary>
/// Metadata on FileProperty table in database
/// </summary>
public readonly struct IndexedFileProperties : IEquatable<IndexedFileProperties>
{
/// <summary>
/// Total indexed FileProperty in database
/// </summary>
public long TotalIndexed { get; init; }

/// <summary>
/// Total sum of all ContentLength rows in FileProperty table
/// </summary>
public long TotalSum { get; init; }

public override bool Equals(object obj) => obj is IndexedFileProperties other && Equals(other);
esmadau marked this conversation as resolved.
Show resolved Hide resolved

public bool Equals(IndexedFileProperties other)
=> TotalIndexed == other.TotalIndexed && TotalSum == other.TotalSum;

public override int GetHashCode()
=> HashCode.Combine(TotalIndexed, TotalSum);

public static bool operator ==(IndexedFileProperties left, IndexedFileProperties right)
=> left.Equals(right);

public static bool operator !=(IndexedFileProperties left, IndexedFileProperties right)
{
return !(left == right);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,11 @@ public interface IIndexDataStore
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that with list of instance metadata with new watermark.</returns>
Task UpdateFilePropertiesContentLengthAsync(IReadOnlyDictionary<long, FileProperties> filePropertiesByWatermark, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves total count in FileProperty table and summation of all content length values across FileProperty table.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that gets the count</returns>
Task<IndexedFileProperties> GetIndexedFileMetricsAsync(CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using OpenTelemetry.Metrics;

namespace Microsoft.Health.Dicom.Core.Features.Telemetry;

/// <summary>
/// Since only enumerators are exposed publicly for working with tags or getting the collection of
/// metrics, these extension facilitate getting both.
/// </summary>
public static class MetricPointExtensions
{
/// <summary>
/// Get tags key value pairs from metric point.
/// </summary>
public static Dictionary<string, object> GetTags(this MetricPoint metricPoint)
{
var tags = new Dictionary<string, object>();
foreach (var pair in metricPoint.Tags)
{
tags.Add(pair.Key, pair.Value);
}

return tags;
}

/// <summary>
/// Get all metrics emitted after flushing.
/// </summary>
#pragma warning disable CA1859 // Prefer using more specific types instead of 'Collection'
esmadau marked this conversation as resolved.
Show resolved Hide resolved
public static IReadOnlyList<MetricPoint> GetMetricPoints(this ICollection<Metric> exportedItems, string metricName)
{
#pragma warning restore CA1859 // Prefer using more specific types instead of 'Collection'
MetricPointsAccessor accessor = exportedItems
.Single(item => item.Name.Equals(metricName, StringComparison.Ordinal))
.GetMetricPoints();
var metrics = new Collection<MetricPoint>();
esmadau marked this conversation as resolved.
Show resolved Hide resolved
foreach (MetricPoint metricPoint in accessor)
{
metrics.Add(metricPoint);
}

return metrics;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PackageReference Include="Microsoft.Health.Operations" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="System.Linq.Async" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.Health.Dicom.Functions.App/host.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
"FirstRetryInterval": "00:01:00",
"MaxNumberOfAttempts": 4
}
},
"IndexMetricsCollection": {
esmadau marked this conversation as resolved.
Show resolved Hide resolved
"Frequency": "0 0 * * *"
}
},
"Extensions": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"IsEncrypted": false,
"Values": {
"AzureFunctionsJobHost__DicomFunctions__Indexing__MaxParallelBatches": "1",
"AzureFunctionsJobHost__DicomFunctions__IndexMetricsCollection__Frequency": "0 0 * * *",
"AzureFunctionsJobHost__Logging__Console__IsEnabled": "true",
"AzureFunctionsJobHost__SqlServer__ConnectionString": "server=(local);Initial Catalog=Dicom;Integrated Security=true;TrustServerCertificate=true",
"AzureFunctionsJobHost__BlobStore__ConnectionString": "UseDevelopmentStorage=true",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Health.Dicom.Core.Configs;
using Microsoft.Health.Dicom.Core.Features.Common;
using Microsoft.Health.Dicom.Core.Features.Store;
using Microsoft.Health.Dicom.Functions.MetricsCollection;
using NSubstitute;
using Xunit;

namespace Microsoft.Health.Dicom.Functions.UnitTests.IndexMetricsCollection;

public class IndexMetricsCollectionFunctionTests
{
private readonly IndexMetricsCollectionFunction _collectionFunction;
private readonly IIndexDataStore _indexStore;
private readonly TimerInfo _timer;

public IndexMetricsCollectionFunctionTests()
{
_indexStore = Substitute.For<IIndexDataStore>();
_collectionFunction = new IndexMetricsCollectionFunction(
_indexStore,
Options.Create(new FeatureConfiguration { EnableExternalStore = true, }));
_timer = Substitute.For<TimerInfo>(default, default, default);
}

[Fact]
public async Task GivenIndexMetricsCollectionFunction_WhenRun_CollectionExecutedWhenExternalStoreEnabled()
{
_indexStore.GetIndexedFileMetricsAsync().ReturnsForAnyArgs(new IndexedFileProperties());

await _collectionFunction.Run(_timer, NullLogger.Instance);

await _indexStore.ReceivedWithAnyArgs(1).GetIndexedFileMetricsAsync();
}

[Fact]
public async Task GivenIndexMetricsCollectionFunction_WhenRun_CollectionNotExecutedWhenExternalStoreNotEnabled()
{
_indexStore.GetIndexedFileMetricsAsync().ReturnsForAnyArgs(new IndexedFileProperties());
var collectionFunctionWihtoutExternalStore = new IndexMetricsCollectionFunction(
_indexStore,
Options.Create(new FeatureConfiguration { EnableExternalStore = false, }));

await collectionFunctionWihtoutExternalStore.Run(_timer, NullLogger.Instance);

await _indexStore.DidNotReceiveWithAnyArgs().GetIndexedFileMetricsAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.Threading.Tasks;
using EnsureThat;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Health.Dicom.Core.Configs;
using Microsoft.Health.Dicom.Core.Features.Common;
using Microsoft.Health.Dicom.Core.Features.Store;
using Microsoft.Health.Functions.Extensions;

namespace Microsoft.Health.Dicom.Functions.MetricsCollection;

/// <summary>
/// A function for collecting index metrics
/// </summary>
public class IndexMetricsCollectionFunction
{
private readonly IIndexDataStore _indexDataStore;
private readonly bool _externalStoreEnabled;
private readonly bool _enableDataPartitions;
private const string RunFrequencyVariable = $"%{AzureFunctionsJobHost.RootSectionName}:DicomFunctions:{IndexMetricsCollectionOptions.SectionName}:{nameof(IndexMetricsCollectionOptions.Frequency)}%";

public IndexMetricsCollectionFunction(
IIndexDataStore indexDataStore,
IOptions<FeatureConfiguration> featureConfiguration)
{
EnsureArg.IsNotNull(featureConfiguration, nameof(featureConfiguration));
_indexDataStore = EnsureArg.IsNotNull(indexDataStore, nameof(indexDataStore));
_externalStoreEnabled = featureConfiguration.Value.EnableExternalStore;
_enableDataPartitions = featureConfiguration.Value.EnableDataPartitions;
}

/// <summary>
/// Asynchronously collects index metrics.
/// </summary>
/// <param name="invocationTimer">The timer which tracks the invocation schedule.</param>
/// <param name="log">A diagnostic logger.</param>
/// <returns>A task that represents the asynchronous metrics collection operation.</returns>
[FunctionName(nameof(IndexMetricsCollectionFunction))]
public async Task Run(
[TimerTrigger(RunFrequencyVariable)] TimerInfo invocationTimer,
ILogger log)
{
EnsureArg.IsNotNull(invocationTimer, nameof(invocationTimer));
EnsureArg.IsNotNull(log, nameof(log));
if (!_externalStoreEnabled)
{
log.LogInformation("External store is not enabled. Skipping index metrics collection.");
return;
}

if (invocationTimer.IsPastDue)
{
log.LogWarning("Current function invocation is running late.");
}
IndexedFileProperties indexedFileProperties = await _indexDataStore.GetIndexedFileMetricsAsync();
esmadau marked this conversation as resolved.
Show resolved Hide resolved

log.LogInformation(
esmadau marked this conversation as resolved.
Show resolved Hide resolved
"DICOM telemetry - TotalFilesIndexed: {TotalFilesIndexed} , TotalByesIndexed: {TotalContentLengthIndexed} , with ExternalStoreEnabled: {ExternalStoreEnabled} and DataPartitionsEnabled: {PartitionsEnabled}",
indexedFileProperties.TotalIndexed,
indexedFileProperties.TotalSum,
_externalStoreEnabled,
_enableDataPartitions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System.ComponentModel.DataAnnotations;

namespace Microsoft.Health.Dicom.Functions.MetricsCollection;

/// <summary>
/// Options on collecting indexing metrics
/// </summary>
public class IndexMetricsCollectionOptions
{
/// <summary>
/// The default section name for <see cref="IndexMetricsCollectionOptions"/> in a configuration.
/// </summary>
public const string SectionName = "IndexMetricsCollection";

/// <summary>
/// Gets or sets the cron expression that indicates how frequently to run the index metrics collection function.
/// </summary>
/// <value>A value cron expression</value>
[Required]
public string Frequency { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Microsoft.Health.Dicom.Functions.DataCleanup;
using Microsoft.Health.Dicom.Functions.Export;
using Microsoft.Health.Dicom.Functions.Indexing;
using Microsoft.Health.Dicom.Functions.MetricsCollection;
using Microsoft.Health.Dicom.Functions.Update;
using Microsoft.Health.Dicom.SqlServer.Registration;
using Microsoft.Health.Extensions.DependencyInjection;
Expand Down Expand Up @@ -67,6 +68,7 @@ public static IDicomFunctionsBuilder ConfigureFunctions(
.AddFunctionsOptions<PurgeHistoryOptions>(configuration, PurgeHistoryOptions.SectionName, isDicomFunction: false)
.AddFunctionsOptions<FeatureConfiguration>(configuration, "DicomServer:Features", isDicomFunction: false)
.AddFunctionsOptions<UpdateOptions>(configuration, UpdateOptions.SectionName)
.AddFunctionsOptions<IndexMetricsCollectionOptions>(configuration, IndexMetricsCollectionOptions.SectionName)
.ConfigureDurableFunctionSerialization()
.AddJsonSerializerOptions(o => o.ConfigureDefaultDicomSettings())
.AddSingleton<UpdateMeter>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
SET XACT_ABORT ON

BEGIN TRANSACTION
GO
CREATE OR ALTER PROCEDURE dbo.GetIndexedFileMetrics
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT TotalIndexedFileCount=COUNT_BIG(*),
TotalIndexedBytes=SUM(ContentLength)
FROM dbo.FileProperty;
END
GO

COMMIT TRANSACTION

IF NOT EXISTS
(
SELECT *
FROM sys.indexes
WHERE NAME = 'IXC_FileProperty_ContentLength'
AND Object_id = OBJECT_ID('dbo.FileProperty')
)
BEGIN
CREATE NONCLUSTERED INDEX IXC_FileProperty_ContentLength ON dbo.FileProperty
(
ContentLength
) WITH (DATA_COMPRESSION = PAGE, ONLINE = ON)
END
GO
Loading
Loading