Skip to content

Commit fb454ab

Browse files
Refactor StorageTransmissionEvaluator and add tests (Azure#27312)
* refactor storagetransmisionevaluator and add tests * change time calculation * use stopwatch instance * rename * minor * extra assert * use stopwatch for calculating export duration * move sw to exporter * rename * typo * resolve pr comments
1 parent 8b92ff8 commit fb454ab

File tree

3 files changed

+211
-48
lines changed

3 files changed

+211
-48
lines changed

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorTraceExporter.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class AzureMonitorTraceExporter : BaseExporter<Activity>
1818
private readonly string _instrumentationKey;
1919
private readonly ResourceParser _resourceParser;
2020
private readonly StorageTransmissionEvaluator _storageTransmissionEvaluator;
21+
private readonly Stopwatch _stopwatch;
2122
private const int StorageTransmissionEvaluatorSampleSize = 10;
2223

2324
public AzureMonitorTraceExporter(AzureMonitorExporterOptions options) : this(options, new AzureMonitorTransmitter(options))
@@ -32,23 +33,25 @@ internal AzureMonitorTraceExporter(AzureMonitorExporterOptions options, ITransmi
3233
_resourceParser = new ResourceParser();
3334

3435
// Todo: Add check if offline storage is enabled by user via options
36+
_stopwatch = Stopwatch.StartNew();
37+
3538
_storageTransmissionEvaluator = new StorageTransmissionEvaluator(StorageTransmissionEvaluatorSampleSize);
3639
}
3740

3841
/// <inheritdoc/>
3942
public override ExportResult Export(in Batch<Activity> batch)
4043
{
44+
// Get export start time
45+
long exportStartTimeInMilliseconds = _stopwatch.ElapsedMilliseconds;
46+
4147
// Add export time interval to data sample
42-
_storageTransmissionEvaluator?.UpdateExportInterval();
48+
_storageTransmissionEvaluator.AddExportIntervalToDataSample(exportStartTimeInMilliseconds);
4349

4450
// Prevent Azure Monitor's HTTP operations from being instrumented.
4551
using var scope = SuppressInstrumentationScope.Begin();
4652

4753
try
4854
{
49-
// Get number ticks before export
50-
long ticksBeforeExport = Stopwatch.GetTimestamp();
51-
5255
var resource = ParentProvider.GetResource();
5356
_resourceParser.UpdateRoleNameAndInstance(resource);
5457
var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(batch, _resourceParser.RoleName, _resourceParser.RoleInstance, _instrumentationKey);
@@ -57,15 +60,15 @@ public override ExportResult Export(in Batch<Activity> batch)
5760
// TODO: Validate CancellationToken and async pattern here.
5861
var exportResult = _transmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
5962

60-
// Get number of ticks after export
61-
long ticksAfterExport = Stopwatch.GetTimestamp();
63+
// Get export end time
64+
long exportEndTimeInMilliseconds = _stopwatch.ElapsedMilliseconds;
6265

6366
// Calculate duration and add it to data sample
64-
double currentExportDuration = TimeSpan.FromTicks(ticksAfterExport - ticksBeforeExport).TotalSeconds;
65-
_storageTransmissionEvaluator.UpdateExportDuration(currentExportDuration);
67+
long currentBatchExportDurationInMilliseconds = exportEndTimeInMilliseconds - exportStartTimeInMilliseconds;
68+
_storageTransmissionEvaluator.AddExportDurationToDataSample(currentBatchExportDurationInMilliseconds);
6669

6770
// Get max number of files we can transmit in this export and start transmitting
68-
long maxFilesToTransmit = _storageTransmissionEvaluator.MaxFilesToTransmitFromStorage();
71+
long maxFilesToTransmit = _storageTransmissionEvaluator.GetMaxFilesToTransmitFromStorage();
6972
_transmitter.TransmitFromStorage(maxFilesToTransmit, false, CancellationToken.None);
7073

7174
return exportResult;
Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
using System;
5-
using System.Diagnostics;
64
using System.Runtime.CompilerServices;
75

86
namespace Azure.Monitor.OpenTelemetry.Exporter
97
{
108
internal class StorageTransmissionEvaluator
119
{
1210
private int _sampleSize;
13-
private double[] _exportDurationsInSeconds;
14-
private double[] _exportIntervalsInSeconds;
11+
private long[] _exportDurationsInMilliseconds;
12+
private long[] _exportIntervalsInMilliseconds;
1513
private int _exportDurationIndex = -1;
1614
private int _exportIntervalIndex = -1;
17-
private long _prevExportTimestampTicks;
18-
private double _currentBatchExportDuration;
19-
private double _exportIntervalRunningSum;
20-
private double _exportDurationRunningSum;
15+
private long _previousExportStartTimeInMilliseconds;
16+
private long _currentBatchExportDurationInMilliseconds;
17+
private long _exportIntervalRunningSum;
18+
private long _exportDurationRunningSum;
2119
private bool _enoughSampleSize;
2220

2321
/// <summary> Initializes a new instance of Storage Transmission Evaluator. </summary>
@@ -26,20 +24,21 @@ internal StorageTransmissionEvaluator(int sampleSize)
2624
{
2725
_sampleSize = sampleSize;
2826

29-
// Array to store time duration in seconds for export
30-
_exportDurationsInSeconds = new double[sampleSize];
27+
// Array to store time duration in Milliseconds for export
28+
_exportDurationsInMilliseconds = new long[sampleSize];
3129

32-
// Array to store time interval in seconds between each export
33-
_exportIntervalsInSeconds = new double[sampleSize];
34-
_prevExportTimestampTicks = Stopwatch.GetTimestamp();
30+
// Array to store time interval in Milliseconds between each export
31+
_exportIntervalsInMilliseconds = new long[sampleSize];
3532
}
3633

3734
/// <summary>
38-
/// Adds current export duration in seconds to the sample size.
35+
/// Adds current export duration in Milliseconds to the sample size.
3936
/// Also, removes the oldest record from the sample.
4037
/// </summary>
41-
internal void UpdateExportDuration(double currentExportDuration)
38+
internal void AddExportDurationToDataSample(long currentBatchExportDurationInMilliseconds)
4239
{
40+
_currentBatchExportDurationInMilliseconds = currentBatchExportDurationInMilliseconds;
41+
4342
_exportDurationIndex++;
4443

4544
// if we run out of elements, start from beginning
@@ -48,48 +47,49 @@ internal void UpdateExportDuration(double currentExportDuration)
4847
_exportDurationIndex = 0;
4948
}
5049

51-
_exportDurationRunningSum -= _exportDurationsInSeconds[_exportDurationIndex];
52-
_exportDurationsInSeconds[_exportDurationIndex] = currentExportDuration;
53-
_exportDurationRunningSum += currentExportDuration;
50+
_exportDurationRunningSum -= _exportDurationsInMilliseconds[_exportDurationIndex];
51+
_exportDurationsInMilliseconds[_exportDurationIndex] = currentBatchExportDurationInMilliseconds;
52+
_exportDurationRunningSum += currentBatchExportDurationInMilliseconds;
5453
}
5554

5655
/// <summary>
57-
/// Adds current export time interval in seconds to the sample size.
56+
/// Adds current export time interval in Milliseconds to the sample size.
5857
/// Also, removes the oldest record from the sample.
5958
/// </summary>
60-
internal void UpdateExportInterval()
59+
internal void AddExportIntervalToDataSample(long currentExportStartTimeInMilliseconds)
6160
{
62-
long curExportTimestampTicks = Stopwatch.GetTimestamp();
63-
64-
// todo: check if this can fail
65-
double exportIntervalSeconds = TimeSpan.FromTicks(curExportTimestampTicks - _prevExportTimestampTicks).TotalSeconds;
61+
long exportIntervalInMilliseconds = (currentExportStartTimeInMilliseconds - _previousExportStartTimeInMilliseconds);
6662

67-
_prevExportTimestampTicks = curExportTimestampTicks;
63+
_previousExportStartTimeInMilliseconds = currentExportStartTimeInMilliseconds;
6864

6965
// If total time elapsed > 2 days
70-
// Set exportIntervalSeconds to 0
66+
// Set exportIntervalInMilliseconds to 0
7167
// This can happen if there was no export in 2 days of application run.
72-
if (exportIntervalSeconds > 172800)
68+
if (exportIntervalInMilliseconds > 172800000)
7369
{
74-
exportIntervalSeconds = 0;
70+
exportIntervalInMilliseconds = 0;
7571
}
7672

7773
_exportIntervalIndex++;
7874

7975
// if we run out of elements, start from beginning
80-
// This also means we now have enough samples to start calculating avg.
8176
if (_exportIntervalIndex == _sampleSize)
8277
{
83-
_enoughSampleSize = true;
8478
_exportIntervalIndex = 0;
8579
}
8680

87-
_exportIntervalRunningSum -= _exportIntervalsInSeconds[_exportIntervalIndex];
88-
_exportIntervalsInSeconds[_exportIntervalIndex] = exportIntervalSeconds;
89-
_exportIntervalRunningSum += exportIntervalSeconds;
81+
// We have now enough samples to calculate avg.
82+
if (!_enoughSampleSize && _exportIntervalIndex == _sampleSize - 1)
83+
{
84+
_enoughSampleSize = true;
85+
}
86+
87+
_exportIntervalRunningSum -= _exportIntervalsInMilliseconds[_exportIntervalIndex];
88+
_exportIntervalsInMilliseconds[_exportIntervalIndex] = exportIntervalInMilliseconds;
89+
_exportIntervalRunningSum += exportIntervalInMilliseconds;
9090
}
9191

92-
internal long MaxFilesToTransmitFromStorage()
92+
internal long GetMaxFilesToTransmitFromStorage()
9393
{
9494
long totalFiles = 0;
9595

@@ -99,25 +99,23 @@ internal long MaxFilesToTransmitFromStorage()
9999
{
100100
double avgDurationPerExport = CalculateAverage(_exportDurationRunningSum, _sampleSize);
101101
double avgExportInterval = CalculateAverage(_exportIntervalRunningSum, _sampleSize);
102-
if (avgExportInterval > _currentBatchExportDuration)
102+
if (avgExportInterval > _currentBatchExportDurationInMilliseconds)
103103
{
104104
// remove currentBatchExportDuration from avg ExportInterval first
105105
// e.g. avg export interval is 10 secs and time it took to export current batch is 5 secs
106106
// we have 5 secs left before we expect next batch
107107
// so, we can transmit 1 file (if avg duration per export is 5 secs)
108-
totalFiles = (long)((avgExportInterval - _currentBatchExportDuration) / avgDurationPerExport);
108+
totalFiles = (long)((avgExportInterval - _currentBatchExportDurationInMilliseconds) / avgDurationPerExport);
109109
}
110110
}
111111

112112
return totalFiles;
113113
}
114114

115115
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116-
private static double CalculateAverage(double sum, int length)
116+
private static double CalculateAverage(long sum, int length)
117117
{
118118
return (sum / length);
119119
}
120-
121-
internal double CurrentBatchExportDuration { get => _currentBatchExportDuration; set => _currentBatchExportDuration = value; }
122120
}
123121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Reflection;
5+
using Xunit;
6+
7+
namespace Azure.Monitor.OpenTelemetry.Exporter.Tests
8+
{
9+
public class StorageTransmissionEvaluatorTests
10+
{
11+
private const int SampleSize = 5;
12+
13+
[Fact]
14+
public void ConstructorInitializesInstanceVariablesForEvaluation()
15+
{
16+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
17+
var sampleSize = typeof(StorageTransmissionEvaluator)
18+
.GetField("_sampleSize", BindingFlags.Instance | BindingFlags.NonPublic)
19+
.GetValue(storageTransmissionEvaluator);
20+
var previousExportStartTimeInMilliseconds = typeof(StorageTransmissionEvaluator)
21+
.GetField("_previousExportStartTimeInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
22+
.GetValue(storageTransmissionEvaluator);
23+
var exportDurationInSeconds = typeof(StorageTransmissionEvaluator)
24+
.GetField("_exportDurationsInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
25+
.GetValue(storageTransmissionEvaluator) as long[];
26+
var exportIntervalsInSeconds = typeof(StorageTransmissionEvaluator)
27+
.GetField("_exportIntervalsInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
28+
.GetValue(storageTransmissionEvaluator) as long[];
29+
30+
Assert.Equal(SampleSize, (int)sampleSize);
31+
Assert.NotNull(exportIntervalsInSeconds);
32+
Assert.NotNull(exportDurationInSeconds);
33+
Assert.Equal(SampleSize, exportIntervalsInSeconds.Length);
34+
Assert.Equal(SampleSize, exportDurationInSeconds.Length);
35+
Assert.Equal(0, (long)previousExportStartTimeInMilliseconds);
36+
}
37+
38+
[Fact]
39+
public void MaxFilesToBeTransmittedIsNonZeroWhenExportDurationIsLessThanExportInterval()
40+
{
41+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
42+
for (int i = 0; i < SampleSize; i++)
43+
{
44+
storageTransmissionEvaluator.AddExportIntervalToDataSample((i+1)*3000);
45+
storageTransmissionEvaluator.AddExportDurationToDataSample(1000);
46+
}
47+
var maxFiles = storageTransmissionEvaluator.GetMaxFilesToTransmitFromStorage();
48+
49+
Assert.True(maxFiles > 0);
50+
}
51+
52+
[Fact]
53+
public void MaxFilesToBeTransmittedIsZeroWhenExportDurationIsGreaterThanExportInterval()
54+
{
55+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
56+
for (int i = 0; i < SampleSize; i++)
57+
{
58+
storageTransmissionEvaluator.AddExportIntervalToDataSample((i+1)*1000);
59+
storageTransmissionEvaluator.AddExportDurationToDataSample(2000);
60+
}
61+
var maxFiles = storageTransmissionEvaluator.GetMaxFilesToTransmitFromStorage();
62+
63+
Assert.Equal(0, maxFiles);
64+
}
65+
66+
[Fact]
67+
public void MaxFilesToBeTransmittedIsZeroWhenCurrentBatchExportDurationIsGreaterThanExportInterval()
68+
{
69+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
70+
71+
for (int i = 0; i < SampleSize; i++)
72+
{
73+
storageTransmissionEvaluator.AddExportIntervalToDataSample((i+1)*3000);
74+
storageTransmissionEvaluator.AddExportDurationToDataSample(1000);
75+
}
76+
77+
// Update the currentBatchExportDuration to a greater value
78+
// than avg export interval.
79+
typeof(StorageTransmissionEvaluator)
80+
.GetField("_currentBatchExportDurationInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
81+
.SetValue(storageTransmissionEvaluator, 10000);
82+
var maxFiles = storageTransmissionEvaluator.GetMaxFilesToTransmitFromStorage();
83+
84+
Assert.Equal(0, maxFiles);
85+
}
86+
87+
[Fact]
88+
public void MaxFilesToBeTransmittedIsZeroWhenDataSamplesAreLessThanSampleSize()
89+
{
90+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
91+
//For Sample Size 5, 5th export will have enough samples to make the decision
92+
for (int i = 0; i < 4; i++)
93+
{
94+
storageTransmissionEvaluator.AddExportDurationToDataSample((i+1)*3000);
95+
storageTransmissionEvaluator.AddExportDurationToDataSample(1000);
96+
}
97+
var maxFiles = storageTransmissionEvaluator.GetMaxFilesToTransmitFromStorage();
98+
99+
Assert.Equal(0, maxFiles);
100+
}
101+
102+
[Fact]
103+
public void ExportIntervalAndDurationsAreUpdatedInCircularManner()
104+
{
105+
var storageTransmissionEvaluator = new StorageTransmissionEvaluator(SampleSize);
106+
long timeInMilliseconds = 2000;
107+
long exportInterval = 0;
108+
109+
// Add data points equal to sample size
110+
for (int i = 0; i < SampleSize; i++)
111+
{
112+
exportInterval += 2000;
113+
storageTransmissionEvaluator.AddExportIntervalToDataSample(exportInterval);
114+
storageTransmissionEvaluator.AddExportDurationToDataSample(timeInMilliseconds);
115+
}
116+
117+
var exportIntervalsInSecondsBefore = typeof(StorageTransmissionEvaluator)
118+
.GetField("_exportIntervalsInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
119+
.GetValue(storageTransmissionEvaluator) as long[];
120+
121+
var exportDurationInSecondsBefore = typeof(StorageTransmissionEvaluator)
122+
.GetField("_exportDurationsInMilliseconds", BindingFlags.Instance | BindingFlags.NonPublic)
123+
.GetValue(storageTransmissionEvaluator) as long[];
124+
125+
// Add data points equal to sample size
126+
for (int i = 0; i < SampleSize; i++)
127+
{
128+
Assert.Equal(2000, exportIntervalsInSecondsBefore[i]);
129+
Assert.Equal(2000, exportDurationInSecondsBefore[i]);
130+
}
131+
132+
// Add 3 more data points with different time values
133+
for (int i = 0; i < 3; i++)
134+
{
135+
exportInterval += 1000;
136+
storageTransmissionEvaluator.AddExportIntervalToDataSample(exportInterval);
137+
storageTransmissionEvaluator.AddExportDurationToDataSample(1000);
138+
}
139+
140+
// First 3 elements should be updated
141+
for (int i = 0; i < 3; i++)
142+
{
143+
Assert.Equal(1000, exportIntervalsInSecondsBefore[i]);
144+
Assert.Equal(1000, exportDurationInSecondsBefore[i]);
145+
}
146+
147+
// Last two should remain the same
148+
for (int i = 3; i < SampleSize; i++)
149+
{
150+
Assert.Equal(2000, exportIntervalsInSecondsBefore[i]);
151+
Assert.Equal(2000, exportDurationInSecondsBefore[i]);
152+
}
153+
154+
// Adding samples more than sample szie should not throw any exception
155+
for (int i = 3; i < 2*SampleSize; i++)
156+
{
157+
storageTransmissionEvaluator.AddExportIntervalToDataSample((i + 1) * timeInMilliseconds);
158+
storageTransmissionEvaluator.AddExportDurationToDataSample(timeInMilliseconds);
159+
}
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)