@fluidframework/telemetry-utils 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.225277

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 (156) hide show
  1. package/.eslintrc.js +12 -13
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +249 -0
  4. package/README.md +68 -1
  5. package/api-extractor-esm.json +5 -0
  6. package/api-extractor-lint.json +4 -0
  7. package/api-extractor.json +2 -2
  8. package/api-report/telemetry-utils.api.md +444 -0
  9. package/dist/config.d.ts +47 -16
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +88 -38
  12. package/dist/config.js.map +1 -1
  13. package/dist/error.d.ts +112 -0
  14. package/dist/error.d.ts.map +1 -0
  15. package/dist/error.js +159 -0
  16. package/dist/error.js.map +1 -0
  17. package/dist/errorLogging.d.ts +86 -20
  18. package/dist/errorLogging.d.ts.map +1 -1
  19. package/dist/errorLogging.js +190 -60
  20. package/dist/errorLogging.js.map +1 -1
  21. package/dist/eventEmitterWithErrorHandling.d.ts +9 -3
  22. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  23. package/dist/eventEmitterWithErrorHandling.js +16 -3
  24. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  25. package/dist/events.d.ts +27 -3
  26. package/dist/events.d.ts.map +1 -1
  27. package/dist/events.js +26 -2
  28. package/dist/events.js.map +1 -1
  29. package/dist/fluidErrorBase.d.ts +57 -16
  30. package/dist/fluidErrorBase.d.ts.map +1 -1
  31. package/dist/fluidErrorBase.js +27 -14
  32. package/dist/fluidErrorBase.js.map +1 -1
  33. package/dist/index.d.ts +12 -11
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +55 -21
  36. package/dist/index.js.map +1 -1
  37. package/dist/logger.d.ts +269 -53
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/logger.js +423 -132
  40. package/dist/logger.js.map +1 -1
  41. package/dist/mockLogger.d.ts +39 -12
  42. package/dist/mockLogger.d.ts.map +1 -1
  43. package/dist/mockLogger.js +105 -22
  44. package/dist/mockLogger.js.map +1 -1
  45. package/dist/sampledTelemetryHelper.d.ts +18 -12
  46. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  47. package/dist/sampledTelemetryHelper.js +28 -19
  48. package/dist/sampledTelemetryHelper.js.map +1 -1
  49. package/dist/telemetry-utils-alpha.d.ts +290 -0
  50. package/dist/telemetry-utils-beta.d.ts +264 -0
  51. package/dist/telemetry-utils-public.d.ts +264 -0
  52. package/dist/telemetry-utils-untrimmed.d.ts +1102 -0
  53. package/dist/telemetryTypes.d.ts +115 -0
  54. package/dist/telemetryTypes.d.ts.map +1 -0
  55. package/dist/telemetryTypes.js +7 -0
  56. package/dist/telemetryTypes.js.map +1 -0
  57. package/dist/thresholdCounter.d.ts +6 -5
  58. package/dist/thresholdCounter.d.ts.map +1 -1
  59. package/dist/thresholdCounter.js +4 -3
  60. package/dist/thresholdCounter.js.map +1 -1
  61. package/dist/tsdoc-metadata.json +11 -0
  62. package/dist/utils.d.ts +54 -3
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +58 -3
  65. package/dist/utils.js.map +1 -1
  66. package/lib/config.d.ts +47 -16
  67. package/lib/config.d.ts.map +1 -1
  68. package/lib/config.js +85 -36
  69. package/lib/config.js.map +1 -1
  70. package/lib/error.d.ts +112 -0
  71. package/lib/error.d.ts.map +1 -0
  72. package/lib/error.js +150 -0
  73. package/lib/error.js.map +1 -0
  74. package/lib/errorLogging.d.ts +86 -20
  75. package/lib/errorLogging.d.ts.map +1 -1
  76. package/lib/errorLogging.js +189 -60
  77. package/lib/errorLogging.js.map +1 -1
  78. package/lib/eventEmitterWithErrorHandling.d.ts +9 -3
  79. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  80. package/lib/eventEmitterWithErrorHandling.js +15 -2
  81. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  82. package/lib/events.d.ts +27 -3
  83. package/lib/events.d.ts.map +1 -1
  84. package/lib/events.js +26 -2
  85. package/lib/events.js.map +1 -1
  86. package/lib/fluidErrorBase.d.ts +57 -16
  87. package/lib/fluidErrorBase.d.ts.map +1 -1
  88. package/lib/fluidErrorBase.js +27 -14
  89. package/lib/fluidErrorBase.js.map +1 -1
  90. package/lib/index.d.ts +12 -11
  91. package/lib/index.d.ts.map +1 -1
  92. package/lib/index.js +11 -11
  93. package/lib/index.js.map +1 -1
  94. package/lib/logger.d.ts +269 -53
  95. package/lib/logger.d.ts.map +1 -1
  96. package/lib/logger.js +415 -131
  97. package/lib/logger.js.map +1 -1
  98. package/lib/mockLogger.d.ts +39 -12
  99. package/lib/mockLogger.d.ts.map +1 -1
  100. package/lib/mockLogger.js +106 -23
  101. package/lib/mockLogger.js.map +1 -1
  102. package/lib/sampledTelemetryHelper.d.ts +18 -12
  103. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  104. package/lib/sampledTelemetryHelper.js +26 -17
  105. package/lib/sampledTelemetryHelper.js.map +1 -1
  106. package/lib/telemetry-utils-alpha.d.mts +290 -0
  107. package/lib/telemetry-utils-beta.d.mts +264 -0
  108. package/lib/telemetry-utils-public.d.mts +264 -0
  109. package/lib/telemetry-utils-untrimmed.d.mts +1102 -0
  110. package/lib/telemetryTypes.d.ts +115 -0
  111. package/lib/telemetryTypes.d.ts.map +1 -0
  112. package/lib/telemetryTypes.js +6 -0
  113. package/lib/telemetryTypes.js.map +1 -0
  114. package/lib/thresholdCounter.d.ts +6 -5
  115. package/lib/thresholdCounter.d.ts.map +1 -1
  116. package/lib/thresholdCounter.js +4 -3
  117. package/lib/thresholdCounter.js.map +1 -1
  118. package/lib/utils.d.ts +54 -3
  119. package/lib/utils.d.ts.map +1 -1
  120. package/lib/utils.js +56 -2
  121. package/lib/utils.js.map +1 -1
  122. package/package.json +86 -57
  123. package/prettier.config.cjs +8 -0
  124. package/src/config.ts +254 -189
  125. package/src/error.ts +235 -0
  126. package/src/errorLogging.ts +440 -290
  127. package/src/eventEmitterWithErrorHandling.ts +26 -14
  128. package/src/events.ts +54 -25
  129. package/src/fluidErrorBase.ts +94 -46
  130. package/src/index.ts +76 -17
  131. package/src/logger.ts +972 -505
  132. package/src/mockLogger.ts +225 -83
  133. package/src/sampledTelemetryHelper.ts +136 -128
  134. package/src/telemetryTypes.ts +140 -0
  135. package/src/thresholdCounter.ts +38 -37
  136. package/src/utils.ts +108 -17
  137. package/tsconfig.esnext.json +6 -6
  138. package/tsconfig.json +9 -13
  139. package/dist/debugLogger.d.ts +0 -39
  140. package/dist/debugLogger.d.ts.map +0 -1
  141. package/dist/debugLogger.js +0 -101
  142. package/dist/debugLogger.js.map +0 -1
  143. package/dist/packageVersion.d.ts +0 -9
  144. package/dist/packageVersion.d.ts.map +0 -1
  145. package/dist/packageVersion.js +0 -12
  146. package/dist/packageVersion.js.map +0 -1
  147. package/lib/debugLogger.d.ts +0 -39
  148. package/lib/debugLogger.d.ts.map +0 -1
  149. package/lib/debugLogger.js +0 -97
  150. package/lib/debugLogger.js.map +0 -1
  151. package/lib/packageVersion.d.ts +0 -9
  152. package/lib/packageVersion.d.ts.map +0 -1
  153. package/lib/packageVersion.js +0 -9
  154. package/lib/packageVersion.js.map +0 -1
  155. package/src/debugLogger.ts +0 -126
  156. package/src/packageVersion.ts +0 -9
package/src/mockLogger.ts CHANGED
@@ -3,102 +3,244 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger, ITelemetryBaseEvent } from "@fluidframework/common-definitions";
7
- import { TelemetryLogger } from "./logger";
6
+ import {
7
+ ITelemetryBaseEvent,
8
+ ITelemetryBaseLogger,
9
+ LogLevel,
10
+ } from "@fluidframework/core-interfaces";
11
+ import { assert } from "@fluidframework/core-utils";
12
+ import { ITelemetryLoggerExt, ITelemetryPropertiesExt } from "./telemetryTypes";
13
+ import { createChildLogger } from "./logger";
8
14
 
9
15
  /**
10
16
  * The MockLogger records events sent to it, and then can walk back over those events
11
17
  * searching for a set of expected events to match against the logged events.
18
+ *
19
+ * @internal
12
20
  */
13
- export class MockLogger extends TelemetryLogger implements ITelemetryLogger {
14
- events: ITelemetryBaseEvent[] = [];
15
-
16
- constructor() { super(); }
17
-
18
- clear() {
19
- this.events = [];
20
- }
21
-
22
- send(event: ITelemetryBaseEvent): void {
23
- this.events.push(event);
24
- }
25
-
26
- /**
27
- * Search events logged since the last time matchEvents was called, looking for the given expected
28
- * events in order.
29
- * @param expectedEvents - events in order that are expected to appear in the recorded log.
30
- * These event objects may be subsets of the logged events.
31
- * Note: category is ommitted from the type because it's usually uninteresting and tedious to type.
32
- */
33
- matchEvents(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]): boolean {
34
- const matchedExpectedEventCount = this.getMatchedEventsCount(expectedEvents);
35
- // How many expected events were left over? Hopefully none.
36
- const unmatchedExpectedEventCount = expectedEvents.length - matchedExpectedEventCount;
37
- return unmatchedExpectedEventCount === 0;
38
- }
39
-
40
- /** Asserts that matchEvents is true, and prints the actual/expected output if not */
41
- assertMatch(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string) {
42
- const actualEvents = this.events;
43
- if (!this.matchEvents(expectedEvents)) {
44
- throw new Error(`${message}
21
+ export class MockLogger implements ITelemetryBaseLogger {
22
+ events: ITelemetryBaseEvent[] = [];
23
+
24
+ constructor(public readonly minLogLevel?: LogLevel) {}
25
+
26
+ clear(): void {
27
+ this.events = [];
28
+ }
29
+
30
+ toTelemetryLogger(): ITelemetryLoggerExt {
31
+ return createChildLogger({ logger: this });
32
+ }
33
+
34
+ send(event: ITelemetryBaseEvent): void {
35
+ this.events.push(event);
36
+ }
37
+
38
+ /**
39
+ * Search events logged since the last time matchEvents was called, looking for the given expected
40
+ * events in order.
41
+ * @param expectedEvents - events in order that are expected to appear in the recorded log.
42
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
43
+ * These event objects may be subsets of the logged events.
44
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
45
+ */
46
+ matchEvents(
47
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
48
+ inlineDetailsProp: boolean = false,
49
+ ): boolean {
50
+ const matchedExpectedEventCount = this.getMatchedEventsCount(
51
+ expectedEvents,
52
+ inlineDetailsProp,
53
+ );
54
+ // How many expected events were left over? Hopefully none.
55
+ const unmatchedExpectedEventCount = expectedEvents.length - matchedExpectedEventCount;
56
+ return unmatchedExpectedEventCount === 0;
57
+ }
58
+
59
+ /**
60
+ * Asserts that matchEvents is true, and prints the actual/expected output if not.
61
+ */
62
+ assertMatch(
63
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
64
+ message?: string,
65
+ inlineDetailsProp: boolean = false,
66
+ ): void {
67
+ const actualEvents = this.events;
68
+ if (!this.matchEvents(expectedEvents, inlineDetailsProp)) {
69
+ throw new Error(`${message}
45
70
  expected:
46
71
  ${JSON.stringify(expectedEvents)}
47
72
 
48
73
  actual:
49
74
  ${JSON.stringify(actualEvents)}`);
50
- }
51
- }
52
-
53
- /**
54
- * Search events logged since the last time matchEvents was called, looking for any of the given
55
- * expected events.
56
- * @param expectedEvents - events that are expected to appear in the recorded log.
57
- * These event objects may be subsets of the logged events.
58
- * Note: category is ommitted from the type because it's usually uninteresting and tedious to type.
59
- * @returns if any of the expected events is found.
60
- */
61
- matchAnyEvent(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]): boolean {
62
- const matchedExpectedEventCount = this.getMatchedEventsCount(expectedEvents);
63
- return matchedExpectedEventCount > 0;
64
- }
65
-
66
- /** Asserts that matchAnyEvent is true, and prints the actual/expected output if not */
67
- assertMatchAny(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string) {
68
- const actualEvents = this.events;
69
- if (!this.matchAnyEvent(expectedEvents)) {
70
- throw new Error(`${message}
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Search events logged since the last time matchEvents was called, looking for any of the given
80
+ * expected events.
81
+ * @param expectedEvents - events that are expected to appear in the recorded log.
82
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
83
+ * These event objects may be subsets of the logged events.
84
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
85
+ * @returns if any of the expected events is found.
86
+ */
87
+ matchAnyEvent(
88
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
89
+ inlineDetailsProp: boolean = false,
90
+ ): boolean {
91
+ const matchedExpectedEventCount = this.getMatchedEventsCount(
92
+ expectedEvents,
93
+ inlineDetailsProp,
94
+ );
95
+ return matchedExpectedEventCount > 0;
96
+ }
97
+
98
+ /**
99
+ * Asserts that matchAnyEvent is true, and prints the actual/expected output if not.
100
+ */
101
+ assertMatchAny(
102
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
103
+ message?: string,
104
+ inlineDetailsProp: boolean = false,
105
+ ): void {
106
+ const actualEvents = this.events;
107
+ if (!this.matchAnyEvent(expectedEvents, inlineDetailsProp)) {
108
+ throw new Error(`${message}
71
109
  expected:
72
110
  ${JSON.stringify(expectedEvents)}
73
111
 
74
112
  actual:
75
113
  ${JSON.stringify(actualEvents)}`);
76
- }
77
- }
78
-
79
- private getMatchedEventsCount(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]): number {
80
- let iExpectedEvent = 0;
81
- this.events.forEach((event) => {
82
- if (iExpectedEvent < expectedEvents.length &&
83
- MockLogger.eventsMatch(event, expectedEvents[iExpectedEvent])
84
- ) {
85
- // We found the next expected event; increment
86
- ++iExpectedEvent;
87
- }
88
- });
89
-
90
- // Remove the events so far; next call will just compare subsequent events from here
91
- this.events = [];
92
-
93
- // Return the count of matched events.
94
- return iExpectedEvent;
95
- }
96
-
97
- /**
98
- * Ensure the expected event is a strict subset of the actual event
99
- */
100
- private static eventsMatch(actual: ITelemetryBaseEvent, expected: Omit<ITelemetryBaseEvent, "category">): boolean {
101
- const masked = { ...actual, ...expected };
102
- return JSON.stringify(masked) === JSON.stringify(actual);
103
- }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Search events logged since the last time matchEvents was called, looking only for the given expected
119
+ * events in order.
120
+ * @param expectedEvents - events in order that are expected to be the only events in the recorded log.
121
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
122
+ * These event objects may be subsets of the logged events.
123
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
124
+ */
125
+ matchEventStrict(
126
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
127
+ inlineDetailsProp: boolean = false,
128
+ ): boolean {
129
+ return (
130
+ expectedEvents.length === this.events.length &&
131
+ this.matchEvents(expectedEvents, inlineDetailsProp)
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Asserts that matchEvents is true, and prints the actual/expected output if not
137
+ */
138
+ assertMatchStrict(
139
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
140
+ message?: string,
141
+ inlineDetailsProp: boolean = false,
142
+ ): void {
143
+ const actualEvents = this.events;
144
+ if (!this.matchEventStrict(expectedEvents, inlineDetailsProp)) {
145
+ throw new Error(`${message}
146
+ expected:
147
+ ${JSON.stringify(expectedEvents)}
148
+
149
+ actual:
150
+ ${JSON.stringify(actualEvents)}`);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Asserts that matchAnyEvent is false for the given events, and prints the actual/expected output if not
156
+ */
157
+ assertMatchNone(
158
+ disallowedEvents: Omit<ITelemetryBaseEvent, "category">[],
159
+ message?: string,
160
+ inlineDetailsProp: boolean = false,
161
+ ): void {
162
+ const actualEvents = this.events;
163
+ if (this.matchAnyEvent(disallowedEvents, inlineDetailsProp)) {
164
+ throw new Error(`${message}
165
+ disallowed events:
166
+ ${JSON.stringify(disallowedEvents)}
167
+
168
+ actual:
169
+ ${JSON.stringify(actualEvents)}`);
170
+ }
171
+ }
172
+
173
+ private getMatchedEventsCount(
174
+ expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
175
+ inlineDetailsProp: boolean,
176
+ ): number {
177
+ let iExpectedEvent = 0;
178
+ for (const event of this.events) {
179
+ if (
180
+ iExpectedEvent < expectedEvents.length &&
181
+ MockLogger.eventsMatch(event, expectedEvents[iExpectedEvent], inlineDetailsProp)
182
+ ) {
183
+ // We found the next expected event; increment
184
+ ++iExpectedEvent;
185
+ }
186
+ }
187
+
188
+ // Remove the events so far; next call will just compare subsequent events from here
189
+ this.events = [];
190
+
191
+ // Return the count of matched events.
192
+ return iExpectedEvent;
193
+ }
194
+
195
+ /**
196
+ * Ensure the expected event is a strict subset of the actual event
197
+ */
198
+ private static eventsMatch(
199
+ actual: ITelemetryBaseEvent,
200
+ expected: Omit<ITelemetryBaseEvent, "category">,
201
+ inlineDetailsProp: boolean,
202
+ ): boolean {
203
+ const { details, ...actualForMatching } = actual;
204
+ // "details" is used in a lot of telemetry logs to group a bunch of properties together and stringify them.
205
+ // Some of the properties in the expected event may be inside "details". So, if inlineDetailsProp is true,
206
+ // extract the properties from "details" in the actual event and inline them in the actual event.
207
+ if (inlineDetailsProp && details !== undefined) {
208
+ assert(
209
+ typeof details === "string",
210
+ // eslint-disable-next-line unicorn/numeric-separators-style
211
+ 0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,
212
+ );
213
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
214
+ const detailsExpanded = JSON.parse(details);
215
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
216
+ return matchObjects({ ...actualForMatching, ...detailsExpanded }, expected);
217
+ }
218
+ return matchObjects(actual, expected);
219
+ }
220
+ }
221
+
222
+ function matchObjects(actual: ITelemetryPropertiesExt, expected: ITelemetryPropertiesExt): boolean {
223
+ for (const [expectedKey, expectedValue] of Object.entries(expected)) {
224
+ const actualValue = actual[expectedKey];
225
+ if (
226
+ !Array.isArray(expectedValue) &&
227
+ expectedValue !== null &&
228
+ typeof expectedValue === "object"
229
+ ) {
230
+ if (
231
+ Array.isArray(actualValue) ||
232
+ actualValue === null ||
233
+ typeof actualValue !== "object" ||
234
+ !matchObjects(
235
+ actualValue as ITelemetryPropertiesExt,
236
+ expectedValue as ITelemetryPropertiesExt,
237
+ )
238
+ ) {
239
+ return false;
240
+ }
241
+ } else if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {
242
+ return false;
243
+ }
244
+ }
245
+ return true;
104
246
  }
@@ -4,139 +4,147 @@
4
4
  */
5
5
 
6
6
  import {
7
- IDisposable,
8
- ITelemetryGenericEvent,
9
- ITelemetryLogger,
10
- ITelemetryPerformanceEvent,
11
- ITelemetryProperties,
12
- } from "@fluidframework/common-definitions";
13
- import { performance } from "@fluidframework/common-utils";
7
+ ITelemetryGenericEvent,
8
+ ITelemetryPerformanceEvent,
9
+ ITelemetryProperties,
10
+ IDisposable,
11
+ } from "@fluidframework/core-interfaces";
12
+ import { performance } from "@fluid-internal/client-utils";
13
+ import { ITelemetryLoggerExt } from "./telemetryTypes";
14
14
 
15
+ /**
16
+ * @privateRemarks
17
+ *
18
+ * The names of the properties in this interface are the ones that will get stamped in the
19
+ * telemetry event, changes should be considered carefully. The optional properties should
20
+ * only be populated if 'includeAggregateMetrics' is true.
21
+ */
15
22
  interface Measurements {
16
- // The names of the properties in this interface are the ones that will get stamped in the
17
- // telemetry event, changes should be considered carefully. The optional properties should
18
- // only be populated if 'includeAggregateMetrics' is true.
19
-
20
- /**
21
- * The duration of the latest execution.
22
- */
23
- duration: number;
24
-
25
- /**
26
- * The number of executions since the last time an event was generated.
27
- */
28
- count: number;
29
-
30
- /**
31
- * Total duration across all the executions since the last event was generated.
32
- */
33
- totalDuration?: number;
34
-
35
- /**
36
- * Min duration across all the executions since the last event was generated.
37
- */
38
- minDuration?: number;
39
-
40
- /**
41
- * Max duration across all the executions since the last event was generated.
42
- */
43
- maxDuration?: number;
23
+ /**
24
+ * The duration of the latest execution.
25
+ */
26
+ duration: number;
27
+
28
+ /**
29
+ * The number of executions since the last time an event was generated.
30
+ */
31
+ count: number;
32
+
33
+ /**
34
+ * Total duration across all the executions since the last event was generated.
35
+ */
36
+ totalDuration?: number;
37
+
38
+ /**
39
+ * Min duration across all the executions since the last event was generated.
40
+ */
41
+ minDuration?: number;
42
+
43
+ /**
44
+ * Max duration across all the executions since the last event was generated.
45
+ */
46
+ maxDuration?: number;
44
47
  }
45
48
 
46
49
  /**
47
50
  * Helper class that executes a specified code block and writes an
48
- * {@link @fluidframework/common-definitions#ITelemetryPerformanceEvent} to a specified logger every time a specified
49
- * number of executions is reached (or when the class is disposed). The `duration` field in the telemetry event is
50
- * the duration of the latest execution (sample) of the specified function. See the documentation of the
51
- * `includeAggregateMetrics` parameter for additional details that can be included.
51
+ * {@link @fluidframework/core-interfaces#ITelemetryPerformanceEvent} to a specified logger every time a specified
52
+ * number of executions is reached (or when the class is disposed).
53
+ *
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.
57
+ *
58
+ * @internal
52
59
  */
53
- export class SampledTelemetryHelper implements IDisposable {
54
- disposed: boolean = false;
55
-
56
- private readonly measurementsMap = new Map<string, Measurements>();
57
-
58
- /**
59
- * @param eventBase -
60
- * Custom properties to include in the telemetry performance event when it is written.
61
- * @param logger -
62
- * The logger to use to write the telemetry performance event.
63
- * @param sampleThreshold -
64
- * Telemetry performance events will be generated every time we hit this many executions of the code block.
65
- * @param includeAggregateMetrics -
66
- * If set to `true`, the telemetry performance event will include aggregated metrics (total duration, min duration,
67
- * max duration) for all the executions in between generated events.
68
- * @param perBucketProperties -
69
- * Map of strings that represent different buckets (which can be specified when calling the 'measure' method), to
70
- * properties which should be added to the telemetry event for that bucket. If a bucket being measured does not
71
- * have an entry in this map, no additional properties will be added to its telemetry events. The following keys are
72
- * reserved for use by this class: "duration", "count", "totalDuration", "minDuration", "maxDuration". If any of
73
- * them is specified as a key in one of the ITelemetryProperties objects in this map, that key-value pair will be
74
- * ignored.
75
- */
76
- public constructor(
77
- private readonly eventBase: ITelemetryGenericEvent,
78
- private readonly logger: ITelemetryLogger,
79
- private readonly sampleThreshold: number,
80
- private readonly includeAggregateMetrics: boolean = false,
81
- private readonly perBucketProperties = new Map<string, ITelemetryProperties>()) {
82
- }
83
-
84
- /**
85
- * @param codeToMeasure -
86
- * The code to be executed and measured.
87
- * @param bucket -
88
- * A key to track executions of the code block separately. Each different value of this parameter has a separate
89
- * set of executions and metrics tracked by the class. If no such distinction needs to be made, do not provide a
90
- * value.
91
- * @returns Whatever the passed-in code block returns.
92
- */
93
- public measure<T>(codeToMeasure: () => T, bucket: string = ""): T {
94
- const start = performance.now();
95
- const returnValue = codeToMeasure();
96
- const duration = performance.now() - start;
97
-
98
- let m = this.measurementsMap.get(bucket);
99
- if (m === undefined) {
100
- m = { count: 0, duration: -1 };
101
- this.measurementsMap.set(bucket, m);
102
- }
103
- m.count++;
104
- m.duration = duration;
105
-
106
- if (this.includeAggregateMetrics) {
107
- m.totalDuration = (m.totalDuration ?? 0) + duration;
108
- m.minDuration = Math.min(m.minDuration ?? duration, duration);
109
- m.maxDuration = Math.max(m.maxDuration ?? 0, duration);
110
- }
111
-
112
- if (m.count >= this.sampleThreshold) {
113
- this.flushBucket(bucket);
114
- }
115
-
116
- return returnValue;
117
- }
118
-
119
- private flushBucket(bucket: string) {
120
- const measurements = this.measurementsMap.get(bucket);
121
- if (measurements === undefined) {
122
- return;
123
- }
124
-
125
- if (measurements.count !== 0) {
126
- const bucketProperties = this.perBucketProperties.get(bucket);
127
-
128
- const telemetryEvent: ITelemetryPerformanceEvent = {
129
- ...this.eventBase,
130
- ...bucketProperties, // If the bucket doesn't exist and this is undefined, things work as expected
131
- ...measurements,
132
- };
133
-
134
- this.logger.sendPerformanceEvent(telemetryEvent);
135
- this.measurementsMap.delete(bucket);
136
- }
137
- }
138
-
139
- public dispose(error?: Error | undefined): void {
140
- this.measurementsMap.forEach((_, k) => this.flushBucket(k));
141
- }
60
+ export class SampledTelemetryHelper implements IDisposable {
61
+ disposed: boolean = false;
62
+
63
+ private readonly measurementsMap = new Map<string, Measurements>();
64
+
65
+ /**
66
+ * @param eventBase -
67
+ * Custom properties to include in the telemetry performance event when it is written.
68
+ * @param logger -
69
+ * The logger to use to write the telemetry performance event.
70
+ * @param sampleThreshold -
71
+ * Telemetry performance events will be generated every time we hit this many executions of the code block.
72
+ * @param includeAggregateMetrics -
73
+ * If set to `true`, the telemetry performance event will include aggregated metrics (total duration, min duration,
74
+ * max duration) for all the executions in between generated events.
75
+ * @param perBucketProperties -
76
+ * Map of strings that represent different buckets (which can be specified when calling the 'measure' method), to
77
+ * properties which should be added to the telemetry event for that bucket. If a bucket being measured does not
78
+ * have an entry in this map, no additional properties will be added to its telemetry events. The following keys are
79
+ * reserved for use by this class: "duration", "count", "totalDuration", "minDuration", "maxDuration". If any of
80
+ * them is specified as a key in one of the ITelemetryProperties objects in this map, that key-value pair will be
81
+ * ignored.
82
+ */
83
+ public constructor(
84
+ private readonly eventBase: ITelemetryGenericEvent,
85
+ private readonly logger: ITelemetryLoggerExt,
86
+ private readonly sampleThreshold: number,
87
+ private readonly includeAggregateMetrics: boolean = false,
88
+ private readonly perBucketProperties = new Map<string, ITelemetryProperties>(),
89
+ ) {}
90
+
91
+ /**
92
+ * Executes the specified code and keeps track of execution time statistics.
93
+ * If it's been called enough times (the sampleThreshold for the class) then it generates a log message with the necessary information.
94
+ *
95
+ * @param codeToMeasure - The code to be executed and measured.
96
+ * @param bucket - A key to track executions of the code block separately.
97
+ * Each different value of this parameter has a separate set of executions and metrics tracked by the class.
98
+ * If no such distinction needs to be made, do not provide a value.
99
+ * @returns Whatever the passed-in code block returns.
100
+ */
101
+ public measure<T>(codeToMeasure: () => T, bucket: string = ""): T {
102
+ const start = performance.now();
103
+ const returnValue = codeToMeasure();
104
+ const duration = performance.now() - start;
105
+
106
+ let m = this.measurementsMap.get(bucket);
107
+ if (m === undefined) {
108
+ m = { count: 0, duration: -1 };
109
+ this.measurementsMap.set(bucket, m);
110
+ }
111
+ m.count++;
112
+ m.duration = duration;
113
+
114
+ if (this.includeAggregateMetrics) {
115
+ m.totalDuration = (m.totalDuration ?? 0) + duration;
116
+ m.minDuration = Math.min(m.minDuration ?? duration, duration);
117
+ m.maxDuration = Math.max(m.maxDuration ?? 0, duration);
118
+ }
119
+
120
+ if (m.count >= this.sampleThreshold) {
121
+ this.flushBucket(bucket);
122
+ }
123
+
124
+ return returnValue;
125
+ }
126
+
127
+ private flushBucket(bucket: string): void {
128
+ const measurements = this.measurementsMap.get(bucket);
129
+ if (measurements === undefined) {
130
+ return;
131
+ }
132
+
133
+ if (measurements.count !== 0) {
134
+ const bucketProperties = this.perBucketProperties.get(bucket);
135
+
136
+ const telemetryEvent: ITelemetryPerformanceEvent = {
137
+ ...this.eventBase,
138
+ ...bucketProperties, // If the bucket doesn't exist and this is undefined, things work as expected
139
+ ...measurements,
140
+ };
141
+
142
+ this.logger.sendPerformanceEvent(telemetryEvent);
143
+ this.measurementsMap.delete(bucket);
144
+ }
145
+ }
146
+
147
+ public dispose(error?: Error | undefined): void {
148
+ for (const [k] of this.measurementsMap.entries()) this.flushBucket(k);
149
+ }
142
150
  }