1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the MIT License. See License.txt in the project root for license information.
3
+
4
+ #nullable enable
5
+
6
+ using System ;
7
+ using System . Collections . Generic ;
8
+ using System . Diagnostics . Metrics ;
9
+ using System . Threading ;
10
+ using System . Threading . Tasks ;
11
+ using Microsoft . ApplicationInsights ;
12
+ using Microsoft . ApplicationInsights . Extensibility ;
13
+ using Microsoft . ApplicationInsights . Metrics ;
14
+ using Microsoft . Extensions . Hosting ;
15
+ using Microsoft . Extensions . Options ;
16
+ using AIMetric = Microsoft . ApplicationInsights . Metric ;
17
+
18
+ namespace Microsoft . Azure . WebJobs . Script . Diagnostics
19
+ {
20
+ /// <summary>
21
+ /// A meter listener which exports metrics to Application Insights.
22
+ /// </summary>
23
+ public sealed class ApplicationInsightsMeterListener : ITelemetryModule , IAsyncDisposable
24
+ {
25
+ private readonly MeterListener _listener ;
26
+ private readonly ApplicationInsightsMeterOptions _options ;
27
+ private readonly CancellationTokenSource _shutdown = new ( ) ;
28
+
29
+ private Task _exportTask = Task . CompletedTask ;
30
+ private TelemetryClient _client = null ! ;
31
+
32
+ /// <summary>
33
+ /// Initializes a new instance of the <see cref="ApplicationInsightsMeterListener"/> class.
34
+ /// </summary>
35
+ /// <param name="lifetime">The application lifetime.</param>
36
+ /// <param name="options">The options.</param>
37
+ public ApplicationInsightsMeterListener (
38
+ IHostApplicationLifetime lifetime ,
39
+ IOptions < ApplicationInsightsMeterOptions > options )
40
+ {
41
+ ArgumentNullException . ThrowIfNull ( options ) ;
42
+ ArgumentNullException . ThrowIfNull ( lifetime ) ;
43
+
44
+ _shutdown = CancellationTokenSource . CreateLinkedTokenSource ( lifetime . ApplicationStopping ) ;
45
+ _options = options . Value ;
46
+ _listener = new ( )
47
+ {
48
+ InstrumentPublished = ( instrument , listener ) =>
49
+ {
50
+ if ( _options . ShouldListenTo ( instrument ) )
51
+ {
52
+ listener . EnableMeasurementEvents ( instrument , this ) ;
53
+ }
54
+ } ,
55
+ } ;
56
+
57
+ // All of the supported instrument value types.
58
+ _listener . SetMeasurementEventCallback ( CreateCallback < byte > ( ) ) ;
59
+ _listener . SetMeasurementEventCallback ( CreateCallback < short > ( ) ) ;
60
+ _listener . SetMeasurementEventCallback ( CreateCallback < int > ( ) ) ;
61
+ _listener . SetMeasurementEventCallback ( CreateCallback < long > ( ) ) ;
62
+ _listener . SetMeasurementEventCallback ( CreateCallback < float > ( ) ) ;
63
+ _listener . SetMeasurementEventCallback ( CreateCallback < double > ( ) ) ;
64
+ _listener . SetMeasurementEventCallback ( CreateCallback < decimal > ( ) ) ;
65
+ }
66
+
67
+ public void Initialize ( TelemetryConfiguration configuration )
68
+ {
69
+ ArgumentNullException . ThrowIfNull ( configuration ) ;
70
+ _client = new TelemetryClient ( configuration ) ;
71
+ _exportTask = CollectAsync ( _shutdown . Token ) ;
72
+ _listener . Start ( ) ;
73
+ }
74
+
75
+ public async ValueTask DisposeAsync ( )
76
+ {
77
+ _listener . Dispose ( ) ;
78
+
79
+ await _shutdown . CancelNoThrowAsync ( ) ;
80
+ await _exportTask . ConfigureAwait ( false ) ;
81
+ await _client . FlushAsync ( default ) . ConfigureAwait ( false ) ;
82
+ _shutdown . Dispose ( ) ;
83
+ }
84
+
85
+ private static MeasurementCallback < T > CreateCallback < T > ( )
86
+ where T : struct
87
+ {
88
+ return ( instrument , value , tags , state ) =>
89
+ {
90
+ if ( state is not ApplicationInsightsMeterListener listener )
91
+ {
92
+ return ;
93
+ }
94
+
95
+ listener . Publish ( instrument , value , tags ) ;
96
+ } ;
97
+ }
98
+
99
+ private async Task CollectAsync ( CancellationToken cancellation )
100
+ {
101
+ while ( ! cancellation . IsCancellationRequested )
102
+ {
103
+ try
104
+ {
105
+ _listener . RecordObservableInstruments ( ) ;
106
+ await Task . Delay ( _options . CollectInterval , cancellation ) ;
107
+ }
108
+ catch ( Exception ex ) when ( ! ex . IsFatal ( ) )
109
+ {
110
+ // swallow exceptions
111
+ }
112
+ }
113
+ }
114
+
115
+ private void Publish < T > ( Instrument instrument , T value , ReadOnlySpan < KeyValuePair < string , object ? > > tags )
116
+ where T : struct
117
+ {
118
+ if ( instrument is null )
119
+ {
120
+ return ;
121
+ }
122
+
123
+ static bool TrackValue ( AIMetric metric , double d )
124
+ {
125
+ metric . TrackValue ( d ) ;
126
+ return true ;
127
+ }
128
+
129
+ MetricIdentifier identifier = GetIdentifier ( instrument , tags ) ;
130
+ AIMetric metric = _client . GetMetric ( identifier ) ;
131
+
132
+ double d = MetricHelpers . ConvertToDouble ( value ) ;
133
+ if ( tags . Length == 0 )
134
+ {
135
+ metric . TrackValue ( d ) ;
136
+ return ;
137
+ }
138
+
139
+ // All the calls are unrolled to avoid allocations.
140
+ _ = tags . Length switch
141
+ {
142
+ 0 => TrackValue ( metric , d ) , // need to massage return type for switch.
143
+ 1 => metric . TrackValue (
144
+ d ,
145
+ GetValueOrDefault ( tags , 0 ) ) ,
146
+ 2 => metric . TrackValue (
147
+ d ,
148
+ GetValueOrDefault ( tags , 0 ) ,
149
+ GetValueOrDefault ( tags , 1 ) ) ,
150
+ 3 => metric . TrackValue (
151
+ d ,
152
+ GetValueOrDefault ( tags , 0 ) ,
153
+ GetValueOrDefault ( tags , 1 ) ,
154
+ GetValueOrDefault ( tags , 2 ) ) ,
155
+ 4 => metric . TrackValue (
156
+ d ,
157
+ GetValueOrDefault ( tags , 0 ) ,
158
+ GetValueOrDefault ( tags , 1 ) ,
159
+ GetValueOrDefault ( tags , 2 ) ,
160
+ GetValueOrDefault ( tags , 3 ) ) ,
161
+ 5 => metric . TrackValue (
162
+ d ,
163
+ GetValueOrDefault ( tags , 0 ) ,
164
+ GetValueOrDefault ( tags , 1 ) ,
165
+ GetValueOrDefault ( tags , 2 ) ,
166
+ GetValueOrDefault ( tags , 3 ) ,
167
+ GetValueOrDefault ( tags , 4 ) ) ,
168
+ 6 => metric . TrackValue (
169
+ d ,
170
+ GetValueOrDefault ( tags , 0 ) ,
171
+ GetValueOrDefault ( tags , 1 ) ,
172
+ GetValueOrDefault ( tags , 2 ) ,
173
+ GetValueOrDefault ( tags , 3 ) ,
174
+ GetValueOrDefault ( tags , 4 ) ,
175
+ GetValueOrDefault ( tags , 5 ) ) ,
176
+ 7 => metric . TrackValue (
177
+ d ,
178
+ GetValueOrDefault ( tags , 0 ) ,
179
+ GetValueOrDefault ( tags , 1 ) ,
180
+ GetValueOrDefault ( tags , 2 ) ,
181
+ GetValueOrDefault ( tags , 3 ) ,
182
+ GetValueOrDefault ( tags , 4 ) ,
183
+ GetValueOrDefault ( tags , 5 ) ,
184
+ GetValueOrDefault ( tags , 6 ) ) ,
185
+ 8 => metric . TrackValue (
186
+ d ,
187
+ GetValueOrDefault ( tags , 0 ) ,
188
+ GetValueOrDefault ( tags , 1 ) ,
189
+ GetValueOrDefault ( tags , 2 ) ,
190
+ GetValueOrDefault ( tags , 3 ) ,
191
+ GetValueOrDefault ( tags , 4 ) ,
192
+ GetValueOrDefault ( tags , 5 ) ,
193
+ GetValueOrDefault ( tags , 6 ) ,
194
+ GetValueOrDefault ( tags , 7 ) ) ,
195
+ 9 => metric . TrackValue (
196
+ d ,
197
+ GetValueOrDefault ( tags , 0 ) ,
198
+ GetValueOrDefault ( tags , 1 ) ,
199
+ GetValueOrDefault ( tags , 2 ) ,
200
+ GetValueOrDefault ( tags , 3 ) ,
201
+ GetValueOrDefault ( tags , 4 ) ,
202
+ GetValueOrDefault ( tags , 5 ) ,
203
+ GetValueOrDefault ( tags , 6 ) ,
204
+ GetValueOrDefault ( tags , 7 ) ,
205
+ GetValueOrDefault ( tags , 8 ) ) ,
206
+ _ => metric . TrackValue (
207
+ d , /* only track first 10 dimensions */
208
+ GetValueOrDefault ( tags , 0 ) ,
209
+ GetValueOrDefault ( tags , 1 ) ,
210
+ GetValueOrDefault ( tags , 2 ) ,
211
+ GetValueOrDefault ( tags , 3 ) ,
212
+ GetValueOrDefault ( tags , 4 ) ,
213
+ GetValueOrDefault ( tags , 5 ) ,
214
+ GetValueOrDefault ( tags , 6 ) ,
215
+ GetValueOrDefault ( tags , 7 ) ,
216
+ GetValueOrDefault ( tags , 8 ) ,
217
+ GetValueOrDefault ( tags , 9 ) ) ,
218
+ } ;
219
+ }
220
+
221
+ private static MetricIdentifier GetIdentifier (
222
+ Instrument instrument , ReadOnlySpan < KeyValuePair < string , object ? > > tags )
223
+ {
224
+ // App insights only supports up to 10 dimensions. We also want to avoid any extra allocation here, so we
225
+ // use the explicit ctor and not the IList<string> accepting one.
226
+ return new MetricIdentifier (
227
+ instrument . Meter . Name ,
228
+ instrument . Name ,
229
+ tags . Length > 0 ? tags [ 0 ] . Key : null ,
230
+ tags . Length > 1 ? tags [ 1 ] . Key : null ,
231
+ tags . Length > 2 ? tags [ 2 ] . Key : null ,
232
+ tags . Length > 3 ? tags [ 3 ] . Key : null ,
233
+ tags . Length > 4 ? tags [ 4 ] . Key : null ,
234
+ tags . Length > 5 ? tags [ 5 ] . Key : null ,
235
+ tags . Length > 6 ? tags [ 6 ] . Key : null ,
236
+ tags . Length > 7 ? tags [ 7 ] . Key : null ,
237
+ tags . Length > 8 ? tags [ 8 ] . Key : null ,
238
+ tags . Length > 9 ? tags [ 9 ] . Key : null ) ;
239
+ }
240
+
241
+ private static string GetValueOrDefault ( ReadOnlySpan < KeyValuePair < string , object ? > > tags , int index )
242
+ => tags . Length > index && tags [ index ] . Value != null
243
+ ? tags [ index ] . Value ? . ToString ( ) ?? string . Empty : string . Empty ;
244
+ }
245
+ }
0 commit comments