@fluidframework/telemetry-utils 2.1.0-274160 → 2.1.0-276985
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -1
- package/dist/config.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts +1 -0
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js +1 -0
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -1
- package/dist/mockLogger.d.ts +23 -0
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +37 -1
- package/dist/mockLogger.js.map +1 -1
- package/dist/sampledTelemetryHelper.d.ts +59 -6
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js +67 -10
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetryTypes.d.ts +7 -0
- package/dist/telemetryTypes.d.ts.map +1 -1
- package/dist/telemetryTypes.js.map +1 -1
- package/lib/config.d.ts +18 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +38 -0
- package/lib/config.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts +1 -0
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js +1 -0
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/logger.d.ts +4 -0
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +1 -0
- package/lib/logger.js.map +1 -1
- package/lib/mockLogger.d.ts +23 -0
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +35 -0
- package/lib/mockLogger.js.map +1 -1
- package/lib/sampledTelemetryHelper.d.ts +59 -6
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js +67 -10
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetryTypes.d.ts +7 -0
- package/lib/telemetryTypes.d.ts.map +1 -1
- package/lib/telemetryTypes.js.map +1 -1
- package/package.json +8 -8
- package/src/config.ts +56 -0
- package/src/eventEmitterWithErrorHandling.ts +1 -0
- package/src/index.ts +14 -2
- package/src/logger.ts +4 -0
- package/src/mockLogger.ts +36 -0
- package/src/sampledTelemetryHelper.ts +164 -13
- package/src/telemetryTypes.ts +7 -0
- /package/api-report/{telemetry-utils.alpha.api.md → telemetry-utils.legacy.alpha.api.md} +0 -0
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import { performance } from "@fluid-internal/client-utils";
|
|
7
7
|
import type { IDisposable, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
|
|
8
|
+
import { assert } from "@fluidframework/core-utils/internal";
|
|
8
9
|
|
|
10
|
+
import { roundToDecimalPlaces } from "./mathTools.js";
|
|
9
11
|
import type {
|
|
10
12
|
ITelemetryGenericEventExt,
|
|
11
13
|
ITelemetryLoggerExt,
|
|
@@ -44,20 +46,96 @@ interface Measurements {
|
|
|
44
46
|
* Max duration across all the executions since the last event was generated.
|
|
45
47
|
*/
|
|
46
48
|
maxDuration?: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Average duration across all the executions since the last event was generated.
|
|
52
|
+
*/
|
|
53
|
+
averageDuration?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The data that will be logged in the telemetry event.
|
|
58
|
+
*/
|
|
59
|
+
interface LoggerData {
|
|
60
|
+
measurements: Measurements;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The sum of the custom data passed into the logger for each key.
|
|
64
|
+
* Absence of a given key should be interpreted as 0.
|
|
65
|
+
*/
|
|
66
|
+
dataSums: Record<string, number>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The max of the custom data passed into the logger for each key.
|
|
70
|
+
*/
|
|
71
|
+
dataMaxes: Record<string, number>;
|
|
47
72
|
}
|
|
48
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Helper type for an object whose properties are all numbers
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
export type CustomMetrics<TKey> = {
|
|
80
|
+
[K in keyof TKey]: K extends string ? number : never;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Potentially part of the structure of the return value of the function provided to {@link SampledTelemetryHelper.measure}.
|
|
85
|
+
*
|
|
86
|
+
* @see {@link MeasureReturnType} for more details on how this type is used.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export interface ICustomData<T> {
|
|
91
|
+
customData: CustomMetrics<T>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Encapsulates the type-level logic for {@link SampledTelemetryHelper.measure}, to determine the expected return type
|
|
96
|
+
* for the function that method receives (and by extension, its own return type). In words: {@link SampledTelemetryHelper}
|
|
97
|
+
* is optionally provided with two generic types: one for custom metrics, and one for the actual return value of the
|
|
98
|
+
* code that will be measured.
|
|
99
|
+
*
|
|
100
|
+
* - If no generic type is provided for custom metrics, then this type is simply the generic type provided for the actual
|
|
101
|
+
* return value of the measured code (which could be void!).
|
|
102
|
+
* - If a generic type is provided for custom metrics, then this type has a `customData` property whose type matches that
|
|
103
|
+
* generic. Then if the generic type for the actual return value is not void, this type also has a property `returnValue`
|
|
104
|
+
* whose type matches the generic type for the actual return value; if the generic type for the actual return value is
|
|
105
|
+
* void, then this type _forbids_ a `returnValue` property (technically, it can exist but must be undefined in that case),
|
|
106
|
+
* to try to ensure that the caller doesn't accidentally provide a function that actually returns a value.
|
|
107
|
+
*
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
export type MeasureReturnType<TMeasureReturn, TCustomMetrics> = TCustomMetrics extends void
|
|
111
|
+
? TMeasureReturn
|
|
112
|
+
: ICustomData<TCustomMetrics> &
|
|
113
|
+
(TMeasureReturn extends void
|
|
114
|
+
? { [K in "returnValue"]?: never }
|
|
115
|
+
: { returnValue: TMeasureReturn });
|
|
116
|
+
|
|
49
117
|
/**
|
|
50
118
|
* Helper class that executes a specified code block and writes an
|
|
51
119
|
* {@link @fluidframework/core-interfaces#ITelemetryPerformanceEvent} to a specified logger every time a specified
|
|
52
120
|
* number of executions is reached (or when the class is disposed).
|
|
53
121
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
122
|
+
* @remarks
|
|
123
|
+
* The `duration` field in the telemetry event this class generates is the duration of the latest execution (sample)
|
|
124
|
+
* of the specified code block.
|
|
125
|
+
* See the documentation of the `includeAggregateMetrics` parameter for additional details that can be included.
|
|
126
|
+
*
|
|
127
|
+
* @typeParam TMeasurementReturn - The return type (in a vacuum) of the code block that will be measured, ignoring
|
|
128
|
+
* any custom metric data that might be required by this class. E.g., the code might just return a boolean.
|
|
129
|
+
* @typeParam TCustomMetrics - A type that contains the custom properties that will be used by an instance of this class
|
|
130
|
+
* for custom metrics. Each property in this type should be a number.
|
|
57
131
|
*
|
|
58
132
|
* @internal
|
|
59
133
|
*/
|
|
60
|
-
export class SampledTelemetryHelper
|
|
134
|
+
export class SampledTelemetryHelper<
|
|
135
|
+
TMeasureReturn = void,
|
|
136
|
+
TCustomMetrics extends CustomMetrics<TCustomMetrics> = void,
|
|
137
|
+
> implements IDisposable
|
|
138
|
+
{
|
|
61
139
|
private _disposed: boolean = false;
|
|
62
140
|
|
|
63
141
|
/**
|
|
@@ -67,7 +145,7 @@ export class SampledTelemetryHelper implements IDisposable {
|
|
|
67
145
|
return this._disposed;
|
|
68
146
|
}
|
|
69
147
|
|
|
70
|
-
private readonly measurementsMap = new Map<string,
|
|
148
|
+
private readonly measurementsMap = new Map<string, LoggerData>();
|
|
71
149
|
|
|
72
150
|
/**
|
|
73
151
|
* @param eventBase -
|
|
@@ -97,7 +175,12 @@ export class SampledTelemetryHelper implements IDisposable {
|
|
|
97
175
|
|
|
98
176
|
/**
|
|
99
177
|
* Executes the specified code and keeps track of execution time statistics.
|
|
100
|
-
*
|
|
178
|
+
* When it's been called enough times (the sampleThreshold for the class) then it generates a log message with the
|
|
179
|
+
* necessary information.
|
|
180
|
+
*
|
|
181
|
+
* @remarks It's the responsibility of the caller to ensure that the same same set of custom metric properties is
|
|
182
|
+
* provided each time this method is called on a given instance of {@link SampledTelemetryHelper}.
|
|
183
|
+
* Otherwise the final measurements in the telemetry event may not be accurate.
|
|
101
184
|
*
|
|
102
185
|
* @param codeToMeasure - The code to be executed and measured.
|
|
103
186
|
* @param bucket - A key to track executions of the code block separately.
|
|
@@ -105,16 +188,25 @@ export class SampledTelemetryHelper implements IDisposable {
|
|
|
105
188
|
* If no such distinction needs to be made, do not provide a value.
|
|
106
189
|
* @returns Whatever the passed-in code block returns.
|
|
107
190
|
*/
|
|
108
|
-
public measure
|
|
191
|
+
public measure(
|
|
192
|
+
codeToMeasure: () => MeasureReturnType<TMeasureReturn, TCustomMetrics>,
|
|
193
|
+
bucket: string = "",
|
|
194
|
+
): MeasureReturnType<TMeasureReturn, TCustomMetrics> {
|
|
109
195
|
const start = performance.now();
|
|
110
196
|
const returnValue = codeToMeasure();
|
|
111
197
|
const duration = performance.now() - start;
|
|
112
198
|
|
|
113
|
-
let
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
199
|
+
let loggerData = this.measurementsMap.get(bucket);
|
|
200
|
+
if (loggerData === undefined) {
|
|
201
|
+
loggerData = {
|
|
202
|
+
measurements: { count: 0, duration: -1 },
|
|
203
|
+
dataSums: {},
|
|
204
|
+
dataMaxes: {},
|
|
205
|
+
};
|
|
206
|
+
this.measurementsMap.set(bucket, loggerData);
|
|
117
207
|
}
|
|
208
|
+
|
|
209
|
+
const m = loggerData.measurements;
|
|
118
210
|
m.count++;
|
|
119
211
|
m.duration = duration;
|
|
120
212
|
|
|
@@ -124,19 +216,77 @@ export class SampledTelemetryHelper implements IDisposable {
|
|
|
124
216
|
m.maxDuration = Math.max(m.maxDuration ?? 0, duration);
|
|
125
217
|
}
|
|
126
218
|
|
|
219
|
+
if (this.isCustomData(returnValue)) {
|
|
220
|
+
loggerData = this.accumulateCustomData(returnValue.customData, loggerData);
|
|
221
|
+
}
|
|
222
|
+
|
|
127
223
|
if (m.count >= this.sampleThreshold) {
|
|
224
|
+
// Computed separately to avoid multiple division operations.
|
|
225
|
+
if (this.includeAggregateMetrics) {
|
|
226
|
+
m.averageDuration = (m.totalDuration ?? 0) / m.count;
|
|
227
|
+
}
|
|
128
228
|
this.flushBucket(bucket);
|
|
129
229
|
}
|
|
130
230
|
|
|
131
231
|
return returnValue;
|
|
132
232
|
}
|
|
133
233
|
|
|
234
|
+
private isCustomData(data: unknown): data is ICustomData<TCustomMetrics> {
|
|
235
|
+
return (
|
|
236
|
+
typeof data === "object" &&
|
|
237
|
+
data !== null &&
|
|
238
|
+
"customData" in data &&
|
|
239
|
+
typeof data.customData === "object"
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private accumulateCustomData(
|
|
244
|
+
customData: CustomMetrics<TCustomMetrics>,
|
|
245
|
+
loggerData: LoggerData,
|
|
246
|
+
): LoggerData {
|
|
247
|
+
for (const [key, val] of Object.entries(customData)) {
|
|
248
|
+
assert(typeof key === "string", "Key should be a string");
|
|
249
|
+
assert(typeof val === "number", "Value should be a number");
|
|
250
|
+
|
|
251
|
+
loggerData.dataSums[key] = (loggerData.dataSums[key] ?? 0) + val;
|
|
252
|
+
loggerData.dataMaxes[key] = Math.max(
|
|
253
|
+
loggerData.dataMaxes[key] ?? Number.NEGATIVE_INFINITY,
|
|
254
|
+
val,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return loggerData;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private processCustomData(loggerData: LoggerData, count: number): Record<string, number> {
|
|
262
|
+
const processedCustomData: Record<string, number> = {};
|
|
263
|
+
|
|
264
|
+
if (loggerData.dataSums === undefined || loggerData.dataMaxes === undefined) {
|
|
265
|
+
return processedCustomData;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const dataSums = loggerData.dataSums;
|
|
269
|
+
const dataMaxes = loggerData.dataMaxes;
|
|
270
|
+
|
|
271
|
+
for (const [key, val] of Object.entries(dataSums)) {
|
|
272
|
+
// implementation of class guarantees the keys between dataMaxes and dataSums align.
|
|
273
|
+
processedCustomData[`avg_${key}`] = roundToDecimalPlaces(val / count, 6);
|
|
274
|
+
processedCustomData[`max_${key}`] = dataMaxes[key] ?? 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return processedCustomData;
|
|
278
|
+
}
|
|
279
|
+
|
|
134
280
|
private flushBucket(bucket: string): void {
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
281
|
+
const loggerData = this.measurementsMap.get(bucket);
|
|
282
|
+
if (loggerData === undefined) {
|
|
137
283
|
return;
|
|
138
284
|
}
|
|
139
285
|
|
|
286
|
+
const measurements = loggerData.measurements;
|
|
287
|
+
|
|
288
|
+
const processedCustomData = this.processCustomData(loggerData, measurements.count);
|
|
289
|
+
|
|
140
290
|
if (measurements.count !== 0) {
|
|
141
291
|
const bucketProperties = this.perBucketProperties.get(bucket);
|
|
142
292
|
|
|
@@ -144,6 +294,7 @@ export class SampledTelemetryHelper implements IDisposable {
|
|
|
144
294
|
...this.eventBase,
|
|
145
295
|
...bucketProperties, // If the bucket doesn't exist and this is undefined, things work as expected
|
|
146
296
|
...measurements,
|
|
297
|
+
...processedCustomData,
|
|
147
298
|
};
|
|
148
299
|
|
|
149
300
|
this.logger.sendPerformanceEvent(telemetryEvent);
|
package/src/telemetryTypes.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { ITelemetryBaseLogger, LogLevel, Tagged } from "@fluidframework/cor
|
|
|
13
13
|
* error - Error log event, ideally 0 of these are logged during a session
|
|
14
14
|
*
|
|
15
15
|
* performance - Includes duration, and often has _start, _end, or _cancel suffixes for activity tracking
|
|
16
|
+
* @legacy
|
|
16
17
|
* @alpha
|
|
17
18
|
*/
|
|
18
19
|
export type TelemetryEventCategory = "generic" | "error" | "performance";
|
|
@@ -23,6 +24,7 @@ export type TelemetryEventCategory = "generic" | "error" | "performance";
|
|
|
23
24
|
* @remarks
|
|
24
25
|
* Includes extra types beyond {@link @fluidframework/core-interfaces#TelemetryBaseEventPropertyType}, which must be
|
|
25
26
|
* converted before sending to a base logger.
|
|
27
|
+
* @legacy
|
|
26
28
|
* @alpha
|
|
27
29
|
*/
|
|
28
30
|
export type TelemetryEventPropertyTypeExt =
|
|
@@ -48,6 +50,7 @@ export interface ITaggedTelemetryPropertyTypeExt {
|
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
52
|
* JSON-serializable properties, which will be logged with telemetry.
|
|
53
|
+
* @legacy
|
|
51
54
|
* @alpha
|
|
52
55
|
*/
|
|
53
56
|
export type ITelemetryPropertiesExt = Record<
|
|
@@ -78,6 +81,7 @@ export interface ITelemetryEventExt extends ITelemetryPropertiesExt {
|
|
|
78
81
|
/**
|
|
79
82
|
* Informational (non-error) telemetry event
|
|
80
83
|
* @remarks Maps to category = "generic"
|
|
84
|
+
* @legacy
|
|
81
85
|
* @alpha
|
|
82
86
|
*/
|
|
83
87
|
export interface ITelemetryGenericEventExt extends ITelemetryPropertiesExt {
|
|
@@ -96,6 +100,7 @@ export interface ITelemetryGenericEventExt extends ITelemetryPropertiesExt {
|
|
|
96
100
|
/**
|
|
97
101
|
* Error telemetry event.
|
|
98
102
|
* @remarks Maps to category = "error"
|
|
103
|
+
* @legacy
|
|
99
104
|
* @alpha
|
|
100
105
|
*/
|
|
101
106
|
export interface ITelemetryErrorEventExt extends ITelemetryPropertiesExt {
|
|
@@ -108,6 +113,7 @@ export interface ITelemetryErrorEventExt extends ITelemetryPropertiesExt {
|
|
|
108
113
|
/**
|
|
109
114
|
* Performance telemetry event.
|
|
110
115
|
* @remarks Maps to category = "performance"
|
|
116
|
+
* @legacy
|
|
111
117
|
* @alpha
|
|
112
118
|
*/
|
|
113
119
|
export interface ITelemetryPerformanceEventExt extends ITelemetryGenericEventExt {
|
|
@@ -123,6 +129,7 @@ export interface ITelemetryPerformanceEventExt extends ITelemetryGenericEventExt
|
|
|
123
129
|
* @remarks
|
|
124
130
|
* This interface is meant to be used internally within the Fluid Framework,
|
|
125
131
|
* and `ITelemetryBaseLogger` should be used when loggers are passed between layers.
|
|
132
|
+
* @legacy
|
|
126
133
|
* @alpha
|
|
127
134
|
*/
|
|
128
135
|
export interface ITelemetryLoggerExt extends ITelemetryBaseLogger {
|
|
File without changes
|