@fluidframework/telemetry-utils 2.0.0-dev-rc.5.0.0.271045 → 2.0.0-dev-rc.5.0.0.271717

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 (60) hide show
  1. package/dist/config.js +1 -3
  2. package/dist/config.js.map +1 -1
  3. package/dist/error.js +9 -10
  4. package/dist/error.js.map +1 -1
  5. package/dist/errorLogging.js +6 -7
  6. package/dist/errorLogging.js.map +1 -1
  7. package/dist/eventEmitterWithErrorHandling.js +0 -1
  8. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/logger.js +7 -19
  14. package/dist/logger.js.map +1 -1
  15. package/dist/mathTools.d.ts +13 -0
  16. package/dist/mathTools.d.ts.map +1 -0
  17. package/dist/mathTools.js +20 -0
  18. package/dist/mathTools.js.map +1 -0
  19. package/dist/mockLogger.js +1 -2
  20. package/dist/mockLogger.js.map +1 -1
  21. package/dist/sampledTelemetryHelper.js +2 -7
  22. package/dist/sampledTelemetryHelper.js.map +1 -1
  23. package/dist/telemetryEventBatcher.d.ts +87 -0
  24. package/dist/telemetryEventBatcher.d.ts.map +1 -0
  25. package/dist/telemetryEventBatcher.js +109 -0
  26. package/dist/telemetryEventBatcher.js.map +1 -0
  27. package/dist/thresholdCounter.js +0 -3
  28. package/dist/thresholdCounter.js.map +1 -1
  29. package/lib/config.js +1 -3
  30. package/lib/config.js.map +1 -1
  31. package/lib/error.js +9 -10
  32. package/lib/error.js.map +1 -1
  33. package/lib/errorLogging.js +6 -7
  34. package/lib/errorLogging.js.map +1 -1
  35. package/lib/eventEmitterWithErrorHandling.js +0 -1
  36. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  37. package/lib/index.d.ts +1 -0
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js +1 -0
  40. package/lib/index.js.map +1 -1
  41. package/lib/logger.js +7 -19
  42. package/lib/logger.js.map +1 -1
  43. package/lib/mathTools.d.ts +13 -0
  44. package/lib/mathTools.d.ts.map +1 -0
  45. package/lib/mathTools.js +16 -0
  46. package/lib/mathTools.js.map +1 -0
  47. package/lib/mockLogger.js +1 -2
  48. package/lib/mockLogger.js.map +1 -1
  49. package/lib/sampledTelemetryHelper.js +2 -7
  50. package/lib/sampledTelemetryHelper.js.map +1 -1
  51. package/lib/telemetryEventBatcher.d.ts +87 -0
  52. package/lib/telemetryEventBatcher.d.ts.map +1 -0
  53. package/lib/telemetryEventBatcher.js +105 -0
  54. package/lib/telemetryEventBatcher.js.map +1 -0
  55. package/lib/thresholdCounter.js +0 -3
  56. package/lib/thresholdCounter.js.map +1 -1
  57. package/package.json +6 -6
  58. package/src/index.ts +1 -0
  59. package/src/mathTools.ts +16 -0
  60. package/src/telemetryEventBatcher.ts +144 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetryEventBatcher.js","sourceRoot":"","sources":["../src/telemetryEventBatcher.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { performance } from \"@fluid-internal/client-utils\";\n\nimport { roundToDecimalPlaces } from \"./mathTools.js\";\nimport type {\n\tITelemetryGenericEventExt,\n\tITelemetryLoggerExt,\n\tITelemetryPerformanceEventExt,\n} from \"./telemetryTypes.js\";\n\n/**\n * Expected type of the custom data passed into the logger.\n * @internal\n */\nexport interface IMeasuredCodeResult<TKey extends string> {\n\t/**\n\t * Optional properties to log custom data. The set of properties must be the same for all calls to the `measure` function.\n\t */\n\ttelemetryProperties?: { readonly [key in TKey]: number };\n}\n\n/**\n * Telemetry class that measures the execution time of a given piece of code and accumulates user defined telemetry metrics, to finally log an event through the {@link TelemetryEventBatcher.logger | logger} provided to this class when the number of calls to the {@link TelemetryEventBatcher.measure | measure} function reaches the specified by {@link TelemetryEventBatcher.threshold | threshold}.\n *\n * @remarks It is expected to be used for a single event type. If the set of `telemetryProperties` is different for different events, a separate `TelemetryEventBatcher` should be created for each event type.\n * @typeparam TMetrics - The set of keys that should be logged.\n * E.g., `keyof Foo` for logging properties `bar` and `baz` from `type Foo = { bar: number, baz: number }`.\n *\n * @sealed\n * @internal\n */\nexport class TelemetryEventBatcher<TMetrics extends string> {\n\t/**\n\t * Stores the accumulated duration of the code passed into the logger.\n\t */\n\tprivate accumulatedDuration: number = 0;\n\n\t/**\n\t * Stores the sum of the custom data passed into the logger.\n\t */\n\tprivate dataSums: { [key in TMetrics]?: number } = {};\n\n\t/**\n\t * Stores the maximum value of the custom data passed into the logger.\n\t */\n\tprivate dataMaxes: { [key in TMetrics]?: number } = {};\n\n\t/**\n\t * Counter to keep track of the number of times the log function is called.\n\t */\n\tprivate counter = 0;\n\n\tpublic constructor(\n\t\t/**\n\t\t * Custom properties to include in the telemetry performance event when it is written.\n\t\t */\n\t\tprivate readonly eventBase: ITelemetryGenericEventExt,\n\n\t\t/**\n\t\t * The logger to use to write the telemetry performance event.\n\t\t */\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\n\t\t/**\n\t\t * The number of logs to accumulate before sending the data to the logger.\n\t\t */\n\t\tprivate readonly threshold: number,\n\t) {}\n\n\t/**\n\t * Executes the specified code, keeping statistics of its execution time and the telemetry properties it returns, and when the {@link TelemetryEventBatcher.threshold} is reached it logs a performance event which includes the maxes and averages.\n\t * @param codeToMeasure - The code to be executed and measured.\n\t * @param customData - Custom data to be logged.\n\t *\n\t * @returns Whatever the passed-in code block returns.\n\t */\n\tpublic measure<T extends IMeasuredCodeResult<TMetrics>>(codeToMeasure: () => T): T {\n\t\tconst start = performance.now();\n\t\tconst returnValue = codeToMeasure();\n\t\tconst duration = performance.now() - start;\n\n\t\tthis.accumulatedDuration += duration;\n\n\t\tif (returnValue.telemetryProperties) {\n\t\t\tthis.accumulateAndLog(returnValue.telemetryProperties);\n\t\t}\n\n\t\treturn returnValue;\n\t}\n\n\t/**\n\t * Accumulates the custom data and sends it to the logger every {@link TelemetryEventBatcher.threshold} calls.\n\t *\n\t * @param customData -\n\t * A record storing the custom data to be accumulated and eventually logged.\n\t */\n\tprivate accumulateAndLog(customData: Record<TMetrics, number>): void {\n\t\tfor (const key of Object.keys(customData) as TMetrics[]) {\n\t\t\tthis.dataSums[key] = (this.dataSums[key] ?? 0) + customData[key];\n\t\t\tthis.dataMaxes[key] = Math.max(\n\t\t\t\tthis.dataMaxes[key] ?? Number.NEGATIVE_INFINITY,\n\t\t\t\tcustomData[key],\n\t\t\t);\n\t\t}\n\n\t\tthis.counter++;\n\n\t\tif (this.counter >= this.threshold) {\n\t\t\tthis.sendData();\n\t\t}\n\t}\n\n\tprivate sendData(): void {\n\t\tconst telemetryEvent: ITelemetryPerformanceEventExt = {\n\t\t\t...this.eventBase,\n\t\t};\n\n\t\ttelemetryEvent.duration = this.accumulatedDuration /= this.counter;\n\n\t\tfor (const key of Object.keys(this.dataSums) as TMetrics[]) {\n\t\t\tif (this.dataSums[key] !== undefined) {\n\t\t\t\ttelemetryEvent[`avg${key}`] = roundToDecimalPlaces(\n\t\t\t\t\tthis.dataSums[key]! / this.counter,\n\t\t\t\t\t6,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this.dataMaxes[key] !== undefined) {\n\t\t\t\ttelemetryEvent[`max${key}`] = this.dataMaxes[key];\n\t\t\t}\n\t\t}\n\n\t\tthis.logger.sendPerformanceEvent(telemetryEvent);\n\n\t\t// Reset the counter and the data.\n\t\tthis.counter = 0;\n\t\tthis.accumulatedDuration = 0;\n\t\tthis.dataSums = {};\n\t\tthis.dataMaxes = {};\n\t}\n}\n"]}
@@ -8,9 +8,6 @@
8
8
  * @internal
9
9
  */
10
10
  export class ThresholdCounter {
11
- threshold;
12
- logger;
13
- thresholdMultiple;
14
11
  constructor(threshold, logger, thresholdMultiple = threshold) {
15
12
  this.threshold = threshold;
16
13
  this.logger = logger;
@@ -1 +1 @@
1
- {"version":3,"file":"thresholdCounter.js","sourceRoot":"","sources":["../src/thresholdCounter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IAEV;IACA;IACT;IAHT,YACkB,SAAiB,EACjB,MAA2B,EACpC,oBAAoB,SAAS;QAFpB,cAAS,GAAT,SAAS,CAAQ;QACjB,WAAM,GAAN,MAAM,CAAqB;QACpC,sBAAiB,GAAjB,iBAAiB,CAAY;IACnC,CAAC;IAEJ;;OAEG;IACI,IAAI,CAAC,SAAiB,EAAE,KAAa;QAC3C,IAAI,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;YAChC,SAAS;YACT,KAAK;SACL,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,cAAc,CAAC,SAAiB,EAAE,KAAa;QACrD,IAAI,KAAK,KAAK,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;gBAChC,SAAS;gBACT,KAAK;aACL,CAAC,CAAC;YACH,sCAAsC;YACtC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLoggerExt } from \"./telemetryTypes.js\";\n\n/**\n * Utility counter which will send event only if the provided value is above a configured threshold.\n *\n * @internal\n */\nexport class ThresholdCounter {\n\tpublic constructor(\n\t\tprivate readonly threshold: number,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t\tprivate thresholdMultiple = threshold,\n\t) {}\n\n\t/**\n\t * Sends the value if it's above the treshold.\n\t */\n\tpublic send(eventName: string, value: number): void {\n\t\tif (value < this.threshold) {\n\t\t\treturn;\n\t\t}\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName,\n\t\t\tvalue,\n\t\t});\n\t}\n\n\t/**\n\t * Sends the value if it's above the threshold\n\t * and a multiple of the threshold.\n\t *\n\t * To be used in scenarios where we'd like to record a\n\t * threshold violation while reducing telemetry noise.\n\t */\n\tpublic sendIfMultiple(eventName: string, value: number): void {\n\t\tif (value === this.thresholdMultiple) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName,\n\t\t\t\tvalue,\n\t\t\t});\n\t\t\t// reduce number of \"multiple\" events.\n\t\t\tthis.thresholdMultiple = this.thresholdMultiple * 2;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"thresholdCounter.js","sourceRoot":"","sources":["../src/thresholdCounter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IAC5B,YACkB,SAAiB,EACjB,MAA2B,EACpC,oBAAoB,SAAS;QAFpB,cAAS,GAAT,SAAS,CAAQ;QACjB,WAAM,GAAN,MAAM,CAAqB;QACpC,sBAAiB,GAAjB,iBAAiB,CAAY;IACnC,CAAC;IAEJ;;OAEG;IACI,IAAI,CAAC,SAAiB,EAAE,KAAa;QAC3C,IAAI,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;YAChC,SAAS;YACT,KAAK;SACL,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,cAAc,CAAC,SAAiB,EAAE,KAAa;QACrD,IAAI,KAAK,KAAK,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;gBAChC,SAAS;gBACT,KAAK;aACL,CAAC,CAAC;YACH,sCAAsC;YACtC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;QACrD,CAAC;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { ITelemetryLoggerExt } from \"./telemetryTypes.js\";\n\n/**\n * Utility counter which will send event only if the provided value is above a configured threshold.\n *\n * @internal\n */\nexport class ThresholdCounter {\n\tpublic constructor(\n\t\tprivate readonly threshold: number,\n\t\tprivate readonly logger: ITelemetryLoggerExt,\n\t\tprivate thresholdMultiple = threshold,\n\t) {}\n\n\t/**\n\t * Sends the value if it's above the treshold.\n\t */\n\tpublic send(eventName: string, value: number): void {\n\t\tif (value < this.threshold) {\n\t\t\treturn;\n\t\t}\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName,\n\t\t\tvalue,\n\t\t});\n\t}\n\n\t/**\n\t * Sends the value if it's above the threshold\n\t * and a multiple of the threshold.\n\t *\n\t * To be used in scenarios where we'd like to record a\n\t * threshold violation while reducing telemetry noise.\n\t */\n\tpublic sendIfMultiple(eventName: string, value: number): void {\n\t\tif (value === this.thresholdMultiple) {\n\t\t\tthis.logger.sendPerformanceEvent({\n\t\t\t\teventName,\n\t\t\t\tvalue,\n\t\t\t});\n\t\t\t// reduce number of \"multiple\" events.\n\t\t\tthis.thresholdMultiple = this.thresholdMultiple * 2;\n\t\t}\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/telemetry-utils",
3
- "version": "2.0.0-dev-rc.5.0.0.271045",
3
+ "version": "2.0.0-dev-rc.5.0.0.271717",
4
4
  "description": "Collection of telemetry relates utilities for Fluid",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -67,17 +67,17 @@
67
67
  "temp-directory": "nyc/.nyc_output"
68
68
  },
69
69
  "dependencies": {
70
- "@fluid-internal/client-utils": "2.0.0-dev-rc.5.0.0.271045",
71
- "@fluidframework/core-interfaces": "2.0.0-dev-rc.5.0.0.271045",
72
- "@fluidframework/core-utils": "2.0.0-dev-rc.5.0.0.271045",
73
- "@fluidframework/driver-definitions": "2.0.0-dev-rc.5.0.0.271045",
70
+ "@fluid-internal/client-utils": "2.0.0-dev-rc.5.0.0.271717",
71
+ "@fluidframework/core-interfaces": "2.0.0-dev-rc.5.0.0.271717",
72
+ "@fluidframework/core-utils": "2.0.0-dev-rc.5.0.0.271717",
73
+ "@fluidframework/driver-definitions": "2.0.0-dev-rc.5.0.0.271717",
74
74
  "debug": "^4.3.4",
75
75
  "uuid": "^9.0.0"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@arethetypeswrong/cli": "^0.15.2",
79
79
  "@biomejs/biome": "^1.7.3",
80
- "@fluid-internal/mocha-test-setup": "2.0.0-dev-rc.5.0.0.271045",
80
+ "@fluid-internal/mocha-test-setup": "2.0.0-dev-rc.5.0.0.271717",
81
81
  "@fluid-tools/build-cli": "^0.39.0",
82
82
  "@fluidframework/build-common": "^2.0.3",
83
83
  "@fluidframework/build-tools": "^0.39.0",
package/src/index.ts CHANGED
@@ -76,3 +76,4 @@ export {
76
76
  ITelemetryPropertiesExt,
77
77
  TelemetryEventCategory,
78
78
  } from "./telemetryTypes.js";
79
+ export { type IMeasuredCodeResult, TelemetryEventBatcher } from "./telemetryEventBatcher.js";
@@ -0,0 +1,16 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Function to round a number to a specified number of decimal places.
8
+ *
9
+ * @param number - The number to round.
10
+ * @param decimalPlaces - The number of decimal places to round to.
11
+ * @returns The rounded number.
12
+ */
13
+ export function roundToDecimalPlaces(number: number, decimalPlaces: number): number {
14
+ const factor = Math.pow(10, decimalPlaces);
15
+ return Math.round(number * factor) / factor;
16
+ }
@@ -0,0 +1,144 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { performance } from "@fluid-internal/client-utils";
7
+
8
+ import { roundToDecimalPlaces } from "./mathTools.js";
9
+ import type {
10
+ ITelemetryGenericEventExt,
11
+ ITelemetryLoggerExt,
12
+ ITelemetryPerformanceEventExt,
13
+ } from "./telemetryTypes.js";
14
+
15
+ /**
16
+ * Expected type of the custom data passed into the logger.
17
+ * @internal
18
+ */
19
+ export interface IMeasuredCodeResult<TKey extends string> {
20
+ /**
21
+ * Optional properties to log custom data. The set of properties must be the same for all calls to the `measure` function.
22
+ */
23
+ telemetryProperties?: { readonly [key in TKey]: number };
24
+ }
25
+
26
+ /**
27
+ * Telemetry class that measures the execution time of a given piece of code and accumulates user defined telemetry metrics, to finally log an event through the {@link TelemetryEventBatcher.logger | logger} provided to this class when the number of calls to the {@link TelemetryEventBatcher.measure | measure} function reaches the specified by {@link TelemetryEventBatcher.threshold | threshold}.
28
+ *
29
+ * @remarks It is expected to be used for a single event type. If the set of `telemetryProperties` is different for different events, a separate `TelemetryEventBatcher` should be created for each event type.
30
+ * @typeparam TMetrics - The set of keys that should be logged.
31
+ * E.g., `keyof Foo` for logging properties `bar` and `baz` from `type Foo = { bar: number, baz: number }`.
32
+ *
33
+ * @sealed
34
+ * @internal
35
+ */
36
+ export class TelemetryEventBatcher<TMetrics extends string> {
37
+ /**
38
+ * Stores the accumulated duration of the code passed into the logger.
39
+ */
40
+ private accumulatedDuration: number = 0;
41
+
42
+ /**
43
+ * Stores the sum of the custom data passed into the logger.
44
+ */
45
+ private dataSums: { [key in TMetrics]?: number } = {};
46
+
47
+ /**
48
+ * Stores the maximum value of the custom data passed into the logger.
49
+ */
50
+ private dataMaxes: { [key in TMetrics]?: number } = {};
51
+
52
+ /**
53
+ * Counter to keep track of the number of times the log function is called.
54
+ */
55
+ private counter = 0;
56
+
57
+ public constructor(
58
+ /**
59
+ * Custom properties to include in the telemetry performance event when it is written.
60
+ */
61
+ private readonly eventBase: ITelemetryGenericEventExt,
62
+
63
+ /**
64
+ * The logger to use to write the telemetry performance event.
65
+ */
66
+ private readonly logger: ITelemetryLoggerExt,
67
+
68
+ /**
69
+ * The number of logs to accumulate before sending the data to the logger.
70
+ */
71
+ private readonly threshold: number,
72
+ ) {}
73
+
74
+ /**
75
+ * Executes the specified code, keeping statistics of its execution time and the telemetry properties it returns, and when the {@link TelemetryEventBatcher.threshold} is reached it logs a performance event which includes the maxes and averages.
76
+ * @param codeToMeasure - The code to be executed and measured.
77
+ * @param customData - Custom data to be logged.
78
+ *
79
+ * @returns Whatever the passed-in code block returns.
80
+ */
81
+ public measure<T extends IMeasuredCodeResult<TMetrics>>(codeToMeasure: () => T): T {
82
+ const start = performance.now();
83
+ const returnValue = codeToMeasure();
84
+ const duration = performance.now() - start;
85
+
86
+ this.accumulatedDuration += duration;
87
+
88
+ if (returnValue.telemetryProperties) {
89
+ this.accumulateAndLog(returnValue.telemetryProperties);
90
+ }
91
+
92
+ return returnValue;
93
+ }
94
+
95
+ /**
96
+ * Accumulates the custom data and sends it to the logger every {@link TelemetryEventBatcher.threshold} calls.
97
+ *
98
+ * @param customData -
99
+ * A record storing the custom data to be accumulated and eventually logged.
100
+ */
101
+ private accumulateAndLog(customData: Record<TMetrics, number>): void {
102
+ for (const key of Object.keys(customData) as TMetrics[]) {
103
+ this.dataSums[key] = (this.dataSums[key] ?? 0) + customData[key];
104
+ this.dataMaxes[key] = Math.max(
105
+ this.dataMaxes[key] ?? Number.NEGATIVE_INFINITY,
106
+ customData[key],
107
+ );
108
+ }
109
+
110
+ this.counter++;
111
+
112
+ if (this.counter >= this.threshold) {
113
+ this.sendData();
114
+ }
115
+ }
116
+
117
+ private sendData(): void {
118
+ const telemetryEvent: ITelemetryPerformanceEventExt = {
119
+ ...this.eventBase,
120
+ };
121
+
122
+ telemetryEvent.duration = this.accumulatedDuration /= this.counter;
123
+
124
+ for (const key of Object.keys(this.dataSums) as TMetrics[]) {
125
+ if (this.dataSums[key] !== undefined) {
126
+ telemetryEvent[`avg${key}`] = roundToDecimalPlaces(
127
+ this.dataSums[key]! / this.counter,
128
+ 6,
129
+ );
130
+ }
131
+ if (this.dataMaxes[key] !== undefined) {
132
+ telemetryEvent[`max${key}`] = this.dataMaxes[key];
133
+ }
134
+ }
135
+
136
+ this.logger.sendPerformanceEvent(telemetryEvent);
137
+
138
+ // Reset the counter and the data.
139
+ this.counter = 0;
140
+ this.accumulatedDuration = 0;
141
+ this.dataSums = {};
142
+ this.dataMaxes = {};
143
+ }
144
+ }