@fluidframework/telemetry-utils 2.1.0-274160 → 2.1.0-276326

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.
Files changed (66) hide show
  1. package/README.md +9 -0
  2. package/dist/config.d.ts +18 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +40 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/eventEmitterWithErrorHandling.d.ts +1 -0
  7. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  8. package/dist/eventEmitterWithErrorHandling.js +1 -0
  9. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +3 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/legacy.d.ts +1 -1
  15. package/dist/logger.d.ts +4 -0
  16. package/dist/logger.d.ts.map +1 -1
  17. package/dist/logger.js +1 -0
  18. package/dist/logger.js.map +1 -1
  19. package/dist/mockLogger.d.ts +23 -0
  20. package/dist/mockLogger.d.ts.map +1 -1
  21. package/dist/mockLogger.js +37 -1
  22. package/dist/mockLogger.js.map +1 -1
  23. package/dist/sampledTelemetryHelper.d.ts +59 -6
  24. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  25. package/dist/sampledTelemetryHelper.js +67 -10
  26. package/dist/sampledTelemetryHelper.js.map +1 -1
  27. package/dist/telemetryTypes.d.ts +7 -0
  28. package/dist/telemetryTypes.d.ts.map +1 -1
  29. package/dist/telemetryTypes.js.map +1 -1
  30. package/lib/config.d.ts +18 -0
  31. package/lib/config.d.ts.map +1 -1
  32. package/lib/config.js +38 -0
  33. package/lib/config.js.map +1 -1
  34. package/lib/eventEmitterWithErrorHandling.d.ts +1 -0
  35. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  36. package/lib/eventEmitterWithErrorHandling.js +1 -0
  37. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  38. package/lib/index.d.ts +3 -3
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +3 -3
  41. package/lib/index.js.map +1 -1
  42. package/lib/legacy.d.ts +1 -1
  43. package/lib/logger.d.ts +4 -0
  44. package/lib/logger.d.ts.map +1 -1
  45. package/lib/logger.js +1 -0
  46. package/lib/logger.js.map +1 -1
  47. package/lib/mockLogger.d.ts +23 -0
  48. package/lib/mockLogger.d.ts.map +1 -1
  49. package/lib/mockLogger.js +35 -0
  50. package/lib/mockLogger.js.map +1 -1
  51. package/lib/sampledTelemetryHelper.d.ts +59 -6
  52. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  53. package/lib/sampledTelemetryHelper.js +67 -10
  54. package/lib/sampledTelemetryHelper.js.map +1 -1
  55. package/lib/telemetryTypes.d.ts +7 -0
  56. package/lib/telemetryTypes.d.ts.map +1 -1
  57. package/lib/telemetryTypes.js.map +1 -1
  58. package/package.json +8 -8
  59. package/src/config.ts +56 -0
  60. package/src/eventEmitterWithErrorHandling.ts +1 -0
  61. package/src/index.ts +14 -2
  62. package/src/logger.ts +4 -0
  63. package/src/mockLogger.ts +36 -0
  64. package/src/sampledTelemetryHelper.ts +164 -13
  65. package/src/telemetryTypes.ts +7 -0
  66. /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
- * The `duration` field in the telemetry event is the duration of the latest execution (sample) of the specified
55
- * function. See the documentation of the `includeAggregateMetrics` parameter for additional details that can be
56
- * included.
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 implements IDisposable {
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, Measurements>();
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
- * If it's been called enough times (the sampleThreshold for the class) then it generates a log message with the necessary information.
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<T>(codeToMeasure: () => T, bucket: string = ""): T {
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 m = this.measurementsMap.get(bucket);
114
- if (m === undefined) {
115
- m = { count: 0, duration: -1 };
116
- this.measurementsMap.set(bucket, m);
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 measurements = this.measurementsMap.get(bucket);
136
- if (measurements === undefined) {
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);
@@ -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 {