@fluidframework/container-runtime 2.0.0-dev.4.3.0.159619 → 2.0.0-dev.4.4.0.161516

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 (107) hide show
  1. package/dist/connectionTelemetry.d.ts.map +1 -1
  2. package/dist/connectionTelemetry.js +1 -0
  3. package/dist/connectionTelemetry.js.map +1 -1
  4. package/dist/containerRuntime.d.ts.map +1 -1
  5. package/dist/containerRuntime.js +5 -2
  6. package/dist/containerRuntime.js.map +1 -1
  7. package/dist/gc/garbageCollection.d.ts +1 -27
  8. package/dist/gc/garbageCollection.d.ts.map +1 -1
  9. package/dist/gc/garbageCollection.js +30 -227
  10. package/dist/gc/garbageCollection.js.map +1 -1
  11. package/dist/gc/gcHelpers.d.ts +0 -10
  12. package/dist/gc/gcHelpers.d.ts.map +1 -1
  13. package/dist/gc/gcHelpers.js +1 -20
  14. package/dist/gc/gcHelpers.js.map +1 -1
  15. package/dist/gc/gcTelemetry.d.ts +91 -0
  16. package/dist/gc/gcTelemetry.d.ts.map +1 -0
  17. package/dist/gc/gcTelemetry.js +262 -0
  18. package/dist/gc/gcTelemetry.js.map +1 -0
  19. package/dist/gc/index.d.ts +2 -1
  20. package/dist/gc/index.d.ts.map +1 -1
  21. package/dist/gc/index.js +4 -2
  22. package/dist/gc/index.js.map +1 -1
  23. package/dist/opLifecycle/opGroupingManager.js +1 -1
  24. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  25. package/dist/opLifecycle/outbox.js +1 -1
  26. package/dist/opLifecycle/outbox.js.map +1 -1
  27. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  28. package/dist/opLifecycle/remoteMessageProcessor.js +25 -22
  29. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  30. package/dist/packageVersion.d.ts +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/dist/summary/summarizer.d.ts +2 -0
  34. package/dist/summary/summarizer.d.ts.map +1 -1
  35. package/dist/summary/summarizer.js +9 -4
  36. package/dist/summary/summarizer.js.map +1 -1
  37. package/dist/summary/summarizerHeuristics.d.ts +8 -9
  38. package/dist/summary/summarizerHeuristics.d.ts.map +1 -1
  39. package/dist/summary/summarizerHeuristics.js +15 -16
  40. package/dist/summary/summarizerHeuristics.js.map +1 -1
  41. package/dist/summary/summarizerTypes.d.ts +2 -0
  42. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  43. package/dist/summary/summarizerTypes.js.map +1 -1
  44. package/dist/summary/summaryGenerator.d.ts.map +1 -1
  45. package/dist/summary/summaryGenerator.js +4 -3
  46. package/dist/summary/summaryGenerator.js.map +1 -1
  47. package/lib/connectionTelemetry.d.ts.map +1 -1
  48. package/lib/connectionTelemetry.js +1 -0
  49. package/lib/connectionTelemetry.js.map +1 -1
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +5 -2
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/gc/garbageCollection.d.ts +1 -27
  54. package/lib/gc/garbageCollection.d.ts.map +1 -1
  55. package/lib/gc/garbageCollection.js +34 -231
  56. package/lib/gc/garbageCollection.js.map +1 -1
  57. package/lib/gc/gcHelpers.d.ts +0 -10
  58. package/lib/gc/gcHelpers.d.ts.map +1 -1
  59. package/lib/gc/gcHelpers.js +0 -18
  60. package/lib/gc/gcHelpers.js.map +1 -1
  61. package/lib/gc/gcTelemetry.d.ts +91 -0
  62. package/lib/gc/gcTelemetry.d.ts.map +1 -0
  63. package/lib/gc/gcTelemetry.js +257 -0
  64. package/lib/gc/gcTelemetry.js.map +1 -0
  65. package/lib/gc/index.d.ts +2 -1
  66. package/lib/gc/index.d.ts.map +1 -1
  67. package/lib/gc/index.js +2 -1
  68. package/lib/gc/index.js.map +1 -1
  69. package/lib/opLifecycle/opGroupingManager.js +1 -1
  70. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  71. package/lib/opLifecycle/outbox.js +1 -1
  72. package/lib/opLifecycle/outbox.js.map +1 -1
  73. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  74. package/lib/opLifecycle/remoteMessageProcessor.js +25 -22
  75. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  76. package/lib/packageVersion.d.ts +1 -1
  77. package/lib/packageVersion.js +1 -1
  78. package/lib/packageVersion.js.map +1 -1
  79. package/lib/summary/summarizer.d.ts +2 -0
  80. package/lib/summary/summarizer.d.ts.map +1 -1
  81. package/lib/summary/summarizer.js +9 -4
  82. package/lib/summary/summarizer.js.map +1 -1
  83. package/lib/summary/summarizerHeuristics.d.ts +8 -9
  84. package/lib/summary/summarizerHeuristics.d.ts.map +1 -1
  85. package/lib/summary/summarizerHeuristics.js +15 -16
  86. package/lib/summary/summarizerHeuristics.js.map +1 -1
  87. package/lib/summary/summarizerTypes.d.ts +2 -0
  88. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  89. package/lib/summary/summarizerTypes.js.map +1 -1
  90. package/lib/summary/summaryGenerator.d.ts.map +1 -1
  91. package/lib/summary/summaryGenerator.js +4 -3
  92. package/lib/summary/summaryGenerator.js.map +1 -1
  93. package/package.json +15 -15
  94. package/src/connectionTelemetry.ts +1 -0
  95. package/src/containerRuntime.ts +7 -1
  96. package/src/gc/garbageCollection.ts +53 -315
  97. package/src/gc/gcHelpers.ts +1 -38
  98. package/src/gc/gcTelemetry.ts +393 -0
  99. package/src/gc/index.ts +1 -1
  100. package/src/opLifecycle/opGroupingManager.ts +1 -1
  101. package/src/opLifecycle/outbox.ts +2 -2
  102. package/src/opLifecycle/remoteMessageProcessor.ts +37 -28
  103. package/src/packageVersion.ts +1 -1
  104. package/src/summary/summarizer.ts +17 -5
  105. package/src/summary/summarizerHeuristics.ts +15 -16
  106. package/src/summary/summarizerTypes.ts +2 -0
  107. package/src/summary/summaryGenerator.ts +5 -4
@@ -0,0 +1,393 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
7
+ import { IGarbageCollectionData } from "@fluidframework/runtime-definitions";
8
+ import { packagePathToTelemetryProperty } from "@fluidframework/runtime-utils";
9
+ import {
10
+ generateStack,
11
+ MonitoringContext,
12
+ TelemetryDataTag,
13
+ } from "@fluidframework/telemetry-utils";
14
+ import { ICreateContainerMetadata } from "../summary";
15
+ import {
16
+ disableSweepLogKey,
17
+ GCNodeType,
18
+ UnreferencedState,
19
+ IGarbageCollectorConfigs,
20
+ disableTombstoneKey,
21
+ throwOnTombstoneUsageKey,
22
+ throwOnTombstoneLoadKey,
23
+ runSweepKey,
24
+ } from "./gcDefinitions";
25
+ import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
26
+
27
+ type NodeUsageType = "Changed" | "Loaded" | "Revived";
28
+
29
+ /** Properties that are common to IUnreferencedEventProps and INodeUsageProps */
30
+ interface ICommonProps {
31
+ usageType: NodeUsageType;
32
+ completedGCRuns: number;
33
+ isTombstoned: boolean;
34
+ lastSummaryTime?: number;
35
+ fromId?: string;
36
+ viaHandle?: boolean;
37
+ }
38
+
39
+ /** The event that is logged when unreferenced node is used after a certain time. */
40
+ interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps {
41
+ state: UnreferencedState;
42
+ id: string;
43
+ type: GCNodeType;
44
+ unrefTime: number;
45
+ age: number;
46
+ timeout?: number;
47
+ }
48
+
49
+ /** Properties passed to nodeUsed function when a node is used. */
50
+ interface INodeUsageProps extends ICommonProps {
51
+ nodeId: string;
52
+ currentReferenceTimestampMs: number | undefined;
53
+ packagePath: readonly string[] | undefined;
54
+ }
55
+
56
+ /**
57
+ * Encapsulates the logic that tracks the various telemetry logged by the Garbage Collector. There are 4 types of
58
+ * telemetry logged:
59
+ * 1. inactiveObject telemetry - When an inactive node is used - A node that has been unreferenced for inactiveTimeoutMs.
60
+ * 2. sweepReadyObject telemetry - When a sweep ready node is used - A node that has been unreferenced for sweepTimeoutMs.
61
+ * 3. Tombstone telemetry - When a tombstoned node is used - A node that that has been marked as tombstone.
62
+ * 4. Sweep / deleted telemetry - When a node is detected as sweep ready in the sweep phase.
63
+ * 5. Unknown outbound reference telemetry - When a node is referenced but GC is not explicitly notified of it.
64
+ */
65
+ export class GCTelemetryTracker {
66
+ // Keeps track of unreferenced events that are logged for a node. This is used to limit the log generation to one
67
+ // per event per node.
68
+ private readonly loggedUnreferencedEvents: Set<string> = new Set();
69
+ // Queue for unreferenced events that should be logged the next time GC runs.
70
+ private pendingEventsQueue: IUnreferencedEventProps[] = [];
71
+
72
+ constructor(
73
+ private readonly mc: MonitoringContext,
74
+ private readonly configs: Pick<
75
+ IGarbageCollectorConfigs,
76
+ "inactiveTimeoutMs" | "sweepTimeoutMs"
77
+ >,
78
+ private readonly isSummarizerClient: boolean,
79
+ private readonly gcTombstoneEnforcementAllowed: boolean,
80
+ private readonly createContainerMetadata: ICreateContainerMetadata,
81
+ private readonly getNodeType: (nodeId: string) => GCNodeType,
82
+ private readonly getNodeStateTracker: (
83
+ nodeId: string,
84
+ ) => UnreferencedStateTracker | undefined,
85
+ private readonly getNodePackagePath: (
86
+ nodePath: string,
87
+ ) => Promise<readonly string[] | undefined>,
88
+ ) {}
89
+
90
+ /**
91
+ * Returns whether an event should be logged for a node that isn't active anymore. Some scenarios where we won't log:
92
+ * 1. When a DDS is changed or loaded. The corresponding data store's event will be logged instead.
93
+ * 2. An event is logged only once per container instance per event per node.
94
+ */
95
+ private shouldLogNonActiveEvent(
96
+ nodeId: string,
97
+ nodeType: GCNodeType,
98
+ usageType: NodeUsageType,
99
+ nodeStateTracker: UnreferencedStateTracker,
100
+ uniqueEventId: string,
101
+ ) {
102
+ if (nodeStateTracker.state === UnreferencedState.Active) {
103
+ return false;
104
+ }
105
+
106
+ // For sub data store (DDS) nodes, if they are changed or loaded, its data store will also be changed or loaded,
107
+ // so skip logging to make the telemetry less noisy.
108
+ if (nodeType === GCNodeType.SubDataStore && usageType !== "Revived") {
109
+ return false;
110
+ }
111
+ if (nodeType === GCNodeType.Other) {
112
+ return false;
113
+ }
114
+
115
+ if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
116
+ return false;
117
+ }
118
+ return true;
119
+ }
120
+
121
+ /**
122
+ * Called when a node is used. If the node is not active, log an event indicating object is used when its not active.
123
+ */
124
+ public nodeUsed(nodeUsageProps: INodeUsageProps) {
125
+ // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
126
+ // logging as nothing interesting would have happened worth logging.
127
+ // If the node is not unreferenced, skip logging.
128
+ const nodeStateTracker = this.getNodeStateTracker(nodeUsageProps.nodeId);
129
+ if (!nodeStateTracker || nodeUsageProps.currentReferenceTimestampMs === undefined) {
130
+ return;
131
+ }
132
+
133
+ // We log these events once per event per node. A unique id is generated by joining node state (inactive / sweep ready),
134
+ // node's id and usage (loaded / changed / revived).
135
+ const uniqueEventId = `${nodeStateTracker.state}-${nodeUsageProps.nodeId}-${nodeUsageProps.usageType}`;
136
+ const nodeType = this.getNodeType(nodeUsageProps.nodeId);
137
+ if (
138
+ !this.shouldLogNonActiveEvent(
139
+ nodeUsageProps.nodeId,
140
+ nodeType,
141
+ nodeUsageProps.usageType,
142
+ nodeStateTracker,
143
+ uniqueEventId,
144
+ )
145
+ ) {
146
+ return;
147
+ }
148
+
149
+ // Add the unique event id so that we don't generate a log for this event again in this session..
150
+ this.loggedUnreferencedEvents.add(uniqueEventId);
151
+
152
+ const state = nodeStateTracker.state;
153
+ const { usageType, currentReferenceTimestampMs, packagePath, ...propsToLog } =
154
+ nodeUsageProps;
155
+ const eventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
156
+ id: nodeUsageProps.nodeId,
157
+ type: nodeType,
158
+ unrefTime: nodeStateTracker.unreferencedTimestampMs,
159
+ age:
160
+ nodeUsageProps.currentReferenceTimestampMs -
161
+ nodeStateTracker.unreferencedTimestampMs,
162
+ timeout:
163
+ state === UnreferencedState.Inactive
164
+ ? this.configs.inactiveTimeoutMs
165
+ : this.configs.sweepTimeoutMs,
166
+ ...propsToLog,
167
+ ...this.createContainerMetadata,
168
+ };
169
+
170
+ // This will log the following events:
171
+ // GC_Tombstone_DataStore_Revived, GC_Tombstone_SubDataStore_Revived, GC_Tombstone_Blob_Revived
172
+ if (nodeUsageProps.usageType === "Revived" && nodeUsageProps.isTombstoned) {
173
+ sendGCUnexpectedUsageEvent(
174
+ this.mc,
175
+ {
176
+ eventName: `GC_Tombstone_${nodeType}_Revived`,
177
+ category: "generic",
178
+ url: nodeUsageProps.nodeId,
179
+ gcTombstoneEnforcementAllowed: this.gcTombstoneEnforcementAllowed,
180
+ },
181
+ undefined /* packagePath */,
182
+ );
183
+ }
184
+
185
+ // For summarizer client, queue the event so it is logged the next time GC runs if the event is still valid.
186
+ // For non-summarizer client, log the event now since GC won't run on it. This may result in false positives
187
+ // but it's a good signal nonetheless and we can consume it with a grain of salt.
188
+ // Inactive errors are usages of Objects that are unreferenced for at least a period of 7 days.
189
+ // SweepReady errors are usages of Objects that will be deleted by GC Sweep!
190
+ if (this.isSummarizerClient) {
191
+ this.pendingEventsQueue.push({
192
+ ...eventProps,
193
+ usageType: nodeUsageProps.usageType,
194
+ state,
195
+ });
196
+ } else {
197
+ // For non-summarizer clients, only log "Loaded" type events since these objects may not be loaded in the
198
+ // summarizer clients if they are based off of user actions (such as scrolling to content for these objects)
199
+ // Events generated:
200
+ // InactiveObject_Loaded, SweepReadyObject_Loaded
201
+ if (nodeUsageProps.usageType === "Loaded") {
202
+ const event = {
203
+ eventName: `${state}Object_${nodeUsageProps.usageType}`,
204
+ pkg: packagePathToTelemetryProperty(nodeUsageProps.packagePath),
205
+ stack: generateStack(),
206
+ ...eventProps,
207
+ };
208
+
209
+ // Do not log the inactive object x events as error events as they are not the best signal for
210
+ // detecting something wrong with GC either from the partner or from the runtime itself.
211
+ if (state === UnreferencedState.Inactive) {
212
+ this.mc.logger.sendTelemetryEvent(event);
213
+ } else {
214
+ this.mc.logger.sendErrorEvent(event);
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Log all new references or outbound routes in the current graph that haven't been explicitly notified to GC.
222
+ * The principle is that every new reference or outbound route must be notified to GC via the
223
+ * addedOutboundReference method. It it hasn't, its a bug and we want to identify these scenarios.
224
+ *
225
+ * In more simple terms:
226
+ * Missing Explicit References = Current References - Previous References - Explicitly Added References;
227
+ *
228
+ * @param currentGCData - The GC data (reference graph) from the current GC run.
229
+ * @param previousGCData - The GC data (reference graph) from the previous GC run.
230
+ * @param explicitReferences - New references added explicity between the previous and the current run.
231
+ */
232
+ public logIfMissingExplicitReferences(
233
+ currentGCData: IGarbageCollectionData,
234
+ previousGCData: IGarbageCollectionData,
235
+ explicitReferences: Map<string, string[]>,
236
+ logger: ITelemetryLogger,
237
+ ) {
238
+ for (const [nodeId, currentOutboundRoutes] of Object.entries(currentGCData.gcNodes)) {
239
+ const previousRoutes = previousGCData.gcNodes[nodeId] ?? [];
240
+ const explicitRoutes = explicitReferences.get(nodeId) ?? [];
241
+
242
+ /**
243
+ * 1. For routes in the current GC data, routes that were not present in previous GC data and did not have
244
+ * explicit references should be added to missing explicit routes list.
245
+ * 2. Only include data store and blob routes since GC only works for these two.
246
+ * Note: Due to a bug with de-duped blobs, only adding data store routes for now.
247
+ * 3. Ignore DDS routes to their parent datastores since those were added implicitly. So, there won't be
248
+ * explicit routes to them.
249
+ */
250
+ const missingExplicitRoutes: string[] = [];
251
+ for (const route of currentOutboundRoutes) {
252
+ const nodeType = this.getNodeType(route);
253
+ if (
254
+ (nodeType === GCNodeType.DataStore || nodeType === GCNodeType.Blob) &&
255
+ !nodeId.startsWith(route) &&
256
+ !previousRoutes.includes(route) &&
257
+ !explicitRoutes.includes(route)
258
+ ) {
259
+ missingExplicitRoutes.push(route);
260
+ }
261
+ }
262
+
263
+ if (missingExplicitRoutes.length > 0) {
264
+ logger.sendErrorEvent({
265
+ eventName: "gcUnknownOutboundReferences",
266
+ gcNodeId: nodeId,
267
+ gcRoutes: JSON.stringify(missingExplicitRoutes),
268
+ });
269
+ }
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Log events that are pending in pendingEventsQueue. This is called after GC runs in the summarizer client
275
+ * so that the state of an unreferenced node is updated.
276
+ */
277
+ public async logPendingEvents(logger: ITelemetryLogger) {
278
+ // Events sent come only from the summarizer client. In between summaries, events are pushed to a queue and at
279
+ // summary time they are then logged.
280
+ // Events generated:
281
+ // InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
282
+ // SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
283
+ for (const eventProps of this.pendingEventsQueue) {
284
+ const { usageType, state, ...propsToLog } = eventProps;
285
+ /**
286
+ * Revived event is logged only if the node is active. If the node is not active, the reference to it was
287
+ * from another unreferenced node and this scenario is not interesting to log.
288
+ * Loaded and Changed events are logged only if the node is not active. If the node is active, it was
289
+ * revived and a Revived event will be logged for it.
290
+ */
291
+ const nodeStateTracker = this.getNodeStateTracker(eventProps.id);
292
+ const active =
293
+ nodeStateTracker === undefined ||
294
+ nodeStateTracker.state === UnreferencedState.Active;
295
+ if ((usageType === "Revived") === active) {
296
+ const pkg = await this.getNodePackagePath(eventProps.id);
297
+ const fromPkg = eventProps.fromId
298
+ ? await this.getNodePackagePath(eventProps.fromId)
299
+ : undefined;
300
+ const event = {
301
+ ...propsToLog,
302
+ eventName: `${state}Object_${usageType}`,
303
+ pkg: pkg
304
+ ? { value: pkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
305
+ : undefined,
306
+ fromPkg: fromPkg
307
+ ? { value: fromPkg.join("/"), tag: TelemetryDataTag.CodeArtifact }
308
+ : undefined,
309
+ };
310
+
311
+ if (state === UnreferencedState.Inactive) {
312
+ logger.sendTelemetryEvent(event);
313
+ } else {
314
+ logger.sendErrorEvent(event);
315
+ }
316
+ }
317
+ }
318
+ this.pendingEventsQueue = [];
319
+ }
320
+
321
+ /**
322
+ * For nodes that are ready to sweep, log an event for now. Until we start running sweep which deletes objects,
323
+ * this will give us a view into how much deleted content a container has.
324
+ */
325
+ public logSweepEvents(
326
+ logger: ITelemetryLogger,
327
+ currentReferenceTimestampMs: number,
328
+ unreferencedNodesState: Map<string, UnreferencedStateTracker>,
329
+ completedGCRuns: number,
330
+ lastSummaryTime?: number,
331
+ ) {
332
+ if (
333
+ this.mc.config.getBoolean(disableSweepLogKey) === true ||
334
+ this.configs.sweepTimeoutMs === undefined
335
+ ) {
336
+ return;
337
+ }
338
+
339
+ for (const [nodeId, nodeStateTracker] of unreferencedNodesState) {
340
+ if (nodeStateTracker.state !== UnreferencedState.SweepReady) {
341
+ return;
342
+ }
343
+
344
+ const nodeType = this.getNodeType(nodeId);
345
+ if (nodeType !== GCNodeType.DataStore && nodeType !== GCNodeType.Blob) {
346
+ return;
347
+ }
348
+
349
+ // Log deleted event for each node only once to reduce noise in telemetry.
350
+ const uniqueEventId = `Deleted-${nodeId}`;
351
+ if (this.loggedUnreferencedEvents.has(uniqueEventId)) {
352
+ return;
353
+ }
354
+ this.loggedUnreferencedEvents.add(uniqueEventId);
355
+ logger.sendTelemetryEvent({
356
+ eventName: "GCObjectDeleted",
357
+ id: nodeId,
358
+ type: nodeType,
359
+ age: currentReferenceTimestampMs - nodeStateTracker.unreferencedTimestampMs,
360
+ timeout: this.configs.sweepTimeoutMs,
361
+ completedGCRuns,
362
+ lastSummaryTime,
363
+ ...this.createContainerMetadata,
364
+ });
365
+ }
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Consolidates info / logic for logging when we encounter unexpected usage of GC'd objects. For example, when a
371
+ * tombstoned or deleted object is loaded.
372
+ */
373
+ export function sendGCUnexpectedUsageEvent(
374
+ mc: MonitoringContext,
375
+ event: ITelemetryGenericEvent & {
376
+ category: "error" | "generic";
377
+ gcTombstoneEnforcementAllowed: boolean | undefined;
378
+ },
379
+ packagePath: readonly string[] | undefined,
380
+ error?: unknown,
381
+ ) {
382
+ event.pkg = packagePathToTelemetryProperty(packagePath);
383
+ event.tombstoneFlags = JSON.stringify({
384
+ DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
385
+ ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
386
+ ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadKey),
387
+ });
388
+ event.sweepFlags = JSON.stringify({
389
+ EnableSweepFlag: mc.config.getBoolean(runSweepKey),
390
+ });
391
+
392
+ mc.logger.sendTelemetryEvent(event, error);
393
+ }
package/src/gc/index.ts CHANGED
@@ -39,7 +39,6 @@ export {
39
39
  cloneGCData,
40
40
  concatGarbageCollectionStates,
41
41
  getGCDataFromSnapshot,
42
- sendGCUnexpectedUsageEvent,
43
42
  shouldAllowGcTombstoneEnforcement,
44
43
  shouldAllowGcSweep,
45
44
  trimLeadingAndTrailingSlashes,
@@ -62,4 +61,5 @@ export {
62
61
  closuresMapLocalStorageKey,
63
62
  SweepReadyUsageDetectionHandler,
64
63
  } from "./gcSweepReadyUsageDetection";
64
+ export { GCTelemetryTracker, sendGCUnexpectedUsageEvent } from "./gcTelemetry";
65
65
  export { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
@@ -39,7 +39,6 @@ export class OpGroupingManager {
39
39
  }
40
40
  }
41
41
 
42
- // Need deserializedContent for back-compat
43
42
  const deserializedContent = {
44
43
  type: OpGroupingManager.groupedBatchOp,
45
44
  contents: batch.content.map<IGroupedMessage>((message) => ({
@@ -56,6 +55,7 @@ export class OpGroupingManager {
56
55
  localOpMetadata: undefined,
57
56
  metadata: undefined,
58
57
  referenceSequenceNumber: batch.content[0].referenceSequenceNumber,
58
+ // Need deserializedContent for back-compat
59
59
  deserializedContent: deserializedContent as ContainerRuntimeMessage,
60
60
  contents: JSON.stringify(deserializedContent),
61
61
  },
@@ -219,8 +219,8 @@ export class Outbox {
219
219
  return this.params.groupingManager.groupBatch(batch);
220
220
  }
221
221
 
222
- const compressedBatch = this.params.groupingManager.groupBatch(
223
- this.params.compressor.compressBatch(batch),
222
+ const compressedBatch = this.params.compressor.compressBatch(
223
+ this.params.groupingManager.groupBatch(batch),
224
224
  );
225
225
 
226
226
  if (this.params.splitter.isBatchChunkingEnabled) {
@@ -27,40 +27,49 @@ export class RemoteMessageProcessor {
27
27
  public process(remoteMessage: ISequencedDocumentMessage): ISequencedDocumentMessage[] {
28
28
  const result: ISequencedDocumentMessage[] = [];
29
29
 
30
- // Ungroup before processing chunks
31
- for (let ungroupedMessage of this.opGroupingManager.ungroupOp(copy(remoteMessage))) {
32
- ungroupedMessage = this.opDecompressor.processMessage(ungroupedMessage).message;
33
- unpackRuntimeMessage(ungroupedMessage);
34
-
35
- const chunkProcessingResult = this.opSplitter.processRemoteMessage(ungroupedMessage);
36
- ungroupedMessage = chunkProcessingResult.message;
37
- if (chunkProcessingResult.state !== "Processed") {
38
- // If the message is not chunked or if the splitter is still rebuilding the original message,
39
- // there is no need to continue processing
40
- result.push(ungroupedMessage);
41
- continue;
42
- }
43
-
44
- // Ungroup the chunked message before decompressing
45
- for (let ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
46
- ungroupedMessage,
47
- )) {
48
- const decompressionAfterChunking = this.opDecompressor.processMessage(
49
- ungroupedMessageAfterChunking,
50
- );
51
- ungroupedMessageAfterChunking = decompressionAfterChunking.message;
52
- if (decompressionAfterChunking.state === "Skipped") {
53
- // After chunking, if the original message was not compressed,
30
+ // Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
31
+ for (const ungroupedMessage of this.opGroupingManager.ungroupOp(copy(remoteMessage))) {
32
+ const message = this.opDecompressor.processMessage(ungroupedMessage).message;
33
+
34
+ for (let ungroupedMessage2 of this.opGroupingManager.ungroupOp(message)) {
35
+ unpackRuntimeMessage(ungroupedMessage2);
36
+
37
+ const chunkProcessingResult =
38
+ this.opSplitter.processRemoteMessage(ungroupedMessage2);
39
+ ungroupedMessage2 = chunkProcessingResult.message;
40
+ if (chunkProcessingResult.state !== "Processed") {
41
+ // If the message is not chunked or if the splitter is still rebuilding the original message,
54
42
  // there is no need to continue processing
55
- result.push(ungroupedMessageAfterChunking);
43
+ result.push(ungroupedMessage2);
56
44
  continue;
57
45
  }
58
46
 
59
- // The message needs to be unpacked after chunking + decompression
60
- unpack(ungroupedMessageAfterChunking);
61
- result.push(ungroupedMessageAfterChunking);
47
+ // Ungroup before and after decompression for back-compat (cleanup tracked by AB#4371)
48
+ for (const ungroupedMessageAfterChunking of this.opGroupingManager.ungroupOp(
49
+ ungroupedMessage2,
50
+ )) {
51
+ const decompressionAfterChunking = this.opDecompressor.processMessage(
52
+ ungroupedMessageAfterChunking,
53
+ );
54
+
55
+ for (const ungroupedMessageAfterChunking2 of this.opGroupingManager.ungroupOp(
56
+ decompressionAfterChunking.message,
57
+ )) {
58
+ if (decompressionAfterChunking.state === "Skipped") {
59
+ // After chunking, if the original message was not compressed,
60
+ // there is no need to continue processing
61
+ result.push(ungroupedMessageAfterChunking2);
62
+ continue;
63
+ }
64
+
65
+ // The message needs to be unpacked after chunking + decompression
66
+ unpack(ungroupedMessageAfterChunking2);
67
+ result.push(ungroupedMessageAfterChunking2);
68
+ }
69
+ }
62
70
  }
63
71
  }
72
+
64
73
  return result;
65
74
  }
66
75
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-dev.4.3.0.159619";
9
+ export const pkgVersion = "2.0.0-dev.4.4.0.161516";
@@ -25,6 +25,7 @@ import { RunningSummarizer } from "./runningSummarizer";
25
25
  import {
26
26
  IConnectableRuntime,
27
27
  ISummarizer,
28
+ ISummarizeHeuristicData,
28
29
  ISummarizerInternalsProvider,
29
30
  ISummarizerRuntime,
30
31
  ISummarizingWarning,
@@ -215,6 +216,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
215
216
  return stopReason === "parentNotConnected";
216
217
  }
217
218
 
219
+ private _heuristicData: ISummarizeHeuristicData | undefined;
220
+
218
221
  /**
219
222
  * Put the summarizer in a started state, including creating and initializing the RunningSummarizer.
220
223
  * The start request can come either from the SummaryManager (in the auto-summarize case) or from the user
@@ -252,17 +255,22 @@ export class Summarizer extends EventEmitter implements ISummarizer {
252
255
  throw new UsageError("clientId should be defined if connected.");
253
256
  }
254
257
 
258
+ this._heuristicData = new SummarizeHeuristicData(
259
+ this.runtime.deltaManager.lastSequenceNumber,
260
+ {
261
+ /** summary attempt baseline for heuristics */
262
+ refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
263
+ summaryTime: Date.now(),
264
+ } as const,
265
+ );
266
+
255
267
  const runningSummarizer = await RunningSummarizer.start(
256
268
  this.logger,
257
269
  this.summaryCollection.createWatcher(clientId),
258
270
  this.configurationGetter(),
259
271
  async (...args) => this.internalsProvider.submitSummary(...args), // submitSummaryCallback
260
272
  async (...args) => this.internalsProvider.refreshLatestSummaryAck(...args), // refreshLatestSummaryCallback
261
- new SummarizeHeuristicData(this.runtime.deltaManager.lastSequenceNumber, {
262
- /** summary attempt baseline for heuristics */
263
- refSequenceNumber: this.runtime.deltaManager.initialSequenceNumber,
264
- summaryTime: Date.now(),
265
- } as const),
273
+ this._heuristicData,
266
274
  this.summaryCollection,
267
275
  runCoordinator /* cancellationToken */,
268
276
  (reason) => runCoordinator.stop(reason) /* stopSummarizerCallback */,
@@ -361,4 +369,8 @@ export class Summarizer extends EventEmitter implements ISummarizer {
361
369
  }
362
370
  return this.runningSummarizer.enqueueSummarize(...args);
363
371
  };
372
+
373
+ public recordSummaryAttempt?(summaryRefSeqNum?: number) {
374
+ this._heuristicData?.recordAttempt(summaryRefSeqNum);
375
+ }
364
376
  }
@@ -27,32 +27,31 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
27
27
  }
28
28
 
29
29
  public get opsSinceLastSummary(): number {
30
- return this.numSystemOpsBefore + this.numNonSystemOpsBefore;
30
+ return this.numNonRuntimeOpsBefore + this.numRuntimeOpsBefore;
31
31
  }
32
32
 
33
- public numNonRuntimeOps: number = 0;
34
- public totalOpsSize: number = 0;
35
33
  public hasMissingOpData: boolean = false;
36
34
 
35
+ public totalOpsSize: number = 0;
37
36
  /**
38
37
  * Cumulative size in bytes of all the ops at the beginning of the summarization attempt.
39
38
  * Is used to adjust totalOpsSize appropriately after successful summarization.
40
39
  */
41
- /** */
42
40
  private totalOpsSizeBefore: number = 0;
43
41
 
42
+ public numNonRuntimeOps: number = 0;
44
43
  /**
45
- * Number of system ops at beginning of attempting to summarize.
46
- * Is used to adjust numSystemOps appropriately after successful summarization.
44
+ * Number of non-runtime ops at beginning of attempting to summarize.
45
+ * Is used to adjust numNonRuntimeOps appropriately after successful summarization.
47
46
  */
48
- private numSystemOpsBefore: number = 0;
47
+ private numNonRuntimeOpsBefore: number = 0;
49
48
 
50
49
  public numRuntimeOps: number = 0;
51
50
  /**
52
- * Number of non-system ops at beginning of attempting to summarize.
53
- * Is used to adjust numNonSystemOps appropriately after successful summarization.
51
+ * Number of runtime ops at beginning of attempting to summarize.
52
+ * Is used to adjust numRuntimeOps appropriately after successful summarization.
54
53
  */
55
- private numNonSystemOpsBefore: number = 0;
54
+ private numRuntimeOpsBefore: number = 0;
56
55
 
57
56
  constructor(
58
57
  public lastOpSequenceNumber: number,
@@ -74,19 +73,19 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
74
73
  summaryTime: Date.now(),
75
74
  };
76
75
 
77
- this.numSystemOpsBefore = this.numNonRuntimeOps;
78
- this.numNonSystemOpsBefore = this.numRuntimeOps;
76
+ this.numNonRuntimeOpsBefore = this.numNonRuntimeOps;
77
+ this.numRuntimeOpsBefore = this.numRuntimeOps;
79
78
  this.totalOpsSizeBefore = this.totalOpsSize;
80
79
  }
81
80
 
82
81
  public markLastAttemptAsSuccessful() {
83
82
  this._lastSuccessfulSummary = { ...this.lastAttempt };
84
83
 
85
- this.numNonRuntimeOps -= this.numSystemOpsBefore;
86
- this.numSystemOpsBefore = 0;
84
+ this.numNonRuntimeOps -= this.numNonRuntimeOpsBefore;
85
+ this.numNonRuntimeOpsBefore = 0;
87
86
 
88
- this.numRuntimeOps -= this.numNonSystemOpsBefore;
89
- this.numNonSystemOpsBefore = 0;
87
+ this.numRuntimeOps -= this.numRuntimeOpsBefore;
88
+ this.numRuntimeOpsBefore = 0;
90
89
 
91
90
  this.totalOpsSize -= this.totalOpsSizeBefore;
92
91
  this.totalOpsSizeBefore = 0;
@@ -489,6 +489,8 @@ type SummaryGeneratorOptionalTelemetryProperties =
489
489
  | "opsSizesSinceLastSummary"
490
490
  /** Delta between the number of non-runtime ops since the last summary */
491
491
  | "nonRuntimeOpsSinceLastSummary"
492
+ /** Delta between the number of runtime ops since the last summary */
493
+ | "runtimeOpsSinceLastSummary"
492
494
  /** Wether or not this instance contains adjusted metrics due to missing op data */
493
495
  | "hasMissingOpData"
494
496
  /** Time it took to generate the summary tree and stats. */