@fluidframework/container-runtime 0.58.2001 → 0.59.1000-61898

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 (124) hide show
  1. package/dist/blobManager.d.ts +15 -2
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +65 -9
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/connectionTelemetry.d.ts.map +1 -1
  6. package/dist/connectionTelemetry.js +63 -23
  7. package/dist/connectionTelemetry.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +39 -7
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +161 -29
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/dataStore.js +8 -1
  13. package/dist/dataStore.js.map +1 -1
  14. package/dist/dataStoreContext.d.ts +9 -3
  15. package/dist/dataStoreContext.d.ts.map +1 -1
  16. package/dist/dataStoreContext.js +22 -6
  17. package/dist/dataStoreContext.js.map +1 -1
  18. package/dist/dataStores.d.ts +13 -5
  19. package/dist/dataStores.d.ts.map +1 -1
  20. package/dist/dataStores.js +39 -18
  21. package/dist/dataStores.js.map +1 -1
  22. package/dist/deltaScheduler.d.ts +4 -5
  23. package/dist/deltaScheduler.d.ts.map +1 -1
  24. package/dist/deltaScheduler.js +54 -35
  25. package/dist/deltaScheduler.js.map +1 -1
  26. package/dist/garbageCollection.d.ts +31 -27
  27. package/dist/garbageCollection.d.ts.map +1 -1
  28. package/dist/garbageCollection.js +76 -75
  29. package/dist/garbageCollection.js.map +1 -1
  30. package/dist/opTelemetry.d.ts +22 -0
  31. package/dist/opTelemetry.d.ts.map +1 -0
  32. package/dist/opTelemetry.js +59 -0
  33. package/dist/opTelemetry.js.map +1 -0
  34. package/dist/orderedClientElection.d.ts +57 -6
  35. package/dist/orderedClientElection.d.ts.map +1 -1
  36. package/dist/orderedClientElection.js +140 -25
  37. package/dist/orderedClientElection.js.map +1 -1
  38. package/dist/packageVersion.d.ts +1 -1
  39. package/dist/packageVersion.d.ts.map +1 -1
  40. package/dist/packageVersion.js +1 -1
  41. package/dist/packageVersion.js.map +1 -1
  42. package/dist/summarizerClientElection.d.ts +2 -0
  43. package/dist/summarizerClientElection.d.ts.map +1 -1
  44. package/dist/summarizerClientElection.js +7 -2
  45. package/dist/summarizerClientElection.js.map +1 -1
  46. package/dist/summarizerTypes.d.ts +9 -0
  47. package/dist/summarizerTypes.d.ts.map +1 -1
  48. package/dist/summarizerTypes.js.map +1 -1
  49. package/dist/summaryGenerator.d.ts.map +1 -1
  50. package/dist/summaryGenerator.js +1 -1
  51. package/dist/summaryGenerator.js.map +1 -1
  52. package/dist/summaryManager.d.ts.map +1 -1
  53. package/dist/summaryManager.js +14 -3
  54. package/dist/summaryManager.js.map +1 -1
  55. package/lib/blobManager.d.ts +15 -2
  56. package/lib/blobManager.d.ts.map +1 -1
  57. package/lib/blobManager.js +66 -10
  58. package/lib/blobManager.js.map +1 -1
  59. package/lib/connectionTelemetry.d.ts.map +1 -1
  60. package/lib/connectionTelemetry.js +63 -23
  61. package/lib/connectionTelemetry.js.map +1 -1
  62. package/lib/containerRuntime.d.ts +39 -7
  63. package/lib/containerRuntime.d.ts.map +1 -1
  64. package/lib/containerRuntime.js +163 -31
  65. package/lib/containerRuntime.js.map +1 -1
  66. package/lib/dataStore.js +8 -1
  67. package/lib/dataStore.js.map +1 -1
  68. package/lib/dataStoreContext.d.ts +9 -3
  69. package/lib/dataStoreContext.d.ts.map +1 -1
  70. package/lib/dataStoreContext.js +22 -6
  71. package/lib/dataStoreContext.js.map +1 -1
  72. package/lib/dataStores.d.ts +13 -5
  73. package/lib/dataStores.d.ts.map +1 -1
  74. package/lib/dataStores.js +39 -18
  75. package/lib/dataStores.js.map +1 -1
  76. package/lib/deltaScheduler.d.ts +4 -5
  77. package/lib/deltaScheduler.d.ts.map +1 -1
  78. package/lib/deltaScheduler.js +54 -35
  79. package/lib/deltaScheduler.js.map +1 -1
  80. package/lib/garbageCollection.d.ts +31 -27
  81. package/lib/garbageCollection.d.ts.map +1 -1
  82. package/lib/garbageCollection.js +75 -74
  83. package/lib/garbageCollection.js.map +1 -1
  84. package/lib/opTelemetry.d.ts +22 -0
  85. package/lib/opTelemetry.d.ts.map +1 -0
  86. package/lib/opTelemetry.js +55 -0
  87. package/lib/opTelemetry.js.map +1 -0
  88. package/lib/orderedClientElection.d.ts +57 -6
  89. package/lib/orderedClientElection.d.ts.map +1 -1
  90. package/lib/orderedClientElection.js +140 -25
  91. package/lib/orderedClientElection.js.map +1 -1
  92. package/lib/packageVersion.d.ts +1 -1
  93. package/lib/packageVersion.d.ts.map +1 -1
  94. package/lib/packageVersion.js +1 -1
  95. package/lib/packageVersion.js.map +1 -1
  96. package/lib/summarizerClientElection.d.ts +2 -0
  97. package/lib/summarizerClientElection.d.ts.map +1 -1
  98. package/lib/summarizerClientElection.js +7 -2
  99. package/lib/summarizerClientElection.js.map +1 -1
  100. package/lib/summarizerTypes.d.ts +9 -0
  101. package/lib/summarizerTypes.d.ts.map +1 -1
  102. package/lib/summarizerTypes.js.map +1 -1
  103. package/lib/summaryGenerator.d.ts.map +1 -1
  104. package/lib/summaryGenerator.js +1 -1
  105. package/lib/summaryGenerator.js.map +1 -1
  106. package/lib/summaryManager.d.ts.map +1 -1
  107. package/lib/summaryManager.js +14 -3
  108. package/lib/summaryManager.js.map +1 -1
  109. package/package.json +63 -19
  110. package/src/blobManager.ts +78 -11
  111. package/src/connectionTelemetry.ts +110 -19
  112. package/src/containerRuntime.ts +191 -36
  113. package/src/dataStore.ts +7 -1
  114. package/src/dataStoreContext.ts +22 -7
  115. package/src/dataStores.ts +40 -19
  116. package/src/deltaScheduler.ts +65 -39
  117. package/src/garbageCollection.ts +92 -78
  118. package/src/opTelemetry.ts +71 -0
  119. package/src/orderedClientElection.ts +154 -25
  120. package/src/packageVersion.ts +1 -1
  121. package/src/summarizerClientElection.ts +7 -2
  122. package/src/summarizerTypes.ts +9 -0
  123. package/src/summaryGenerator.ts +9 -1
  124. package/src/summaryManager.ts +15 -4
@@ -5,13 +5,13 @@
5
5
 
6
6
  import { IFluidHandle, IFluidHandleContext } from "@fluidframework/core-interfaces";
7
7
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
8
- import { AttachmentTreeEntry, BlobTreeEntry } from "@fluidframework/protocol-base";
9
- import { ISnapshotTree, ITree, ITreeEntry } from "@fluidframework/protocol-definitions";
10
- import { generateHandleContextPath } from "@fluidframework/runtime-utils";
8
+ import { ISnapshotTree } from "@fluidframework/protocol-definitions";
9
+ import { generateHandleContextPath, SummaryTreeBuilder } from "@fluidframework/runtime-utils";
11
10
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
12
11
  import { assert, Deferred } from "@fluidframework/common-utils";
13
12
  import { IContainerRuntime } from "@fluidframework/container-runtime-definitions";
14
13
  import { AttachState } from "@fluidframework/container-definitions";
14
+ import { IGarbageCollectionData, ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
15
15
 
16
16
  /**
17
17
  * This class represents blob (long string)
@@ -147,9 +147,12 @@ export class BlobManager {
147
147
  }
148
148
 
149
149
  public processBlobAttachOp(blobId: string, local: boolean) {
150
- assert(!local || this.pendingBlobIds.has(blobId), 0x1f8 /* "local BlobAttach op with no pending blob" */);
151
- this.pendingBlobIds.get(blobId)?.resolve();
152
- this.pendingBlobIds.delete(blobId);
150
+ if (local) {
151
+ const pendingBlobP = this.pendingBlobIds.get(blobId);
152
+ assert(pendingBlobP !== undefined, 0x1f8 /* "local BlobAttach op with no pending blob" */);
153
+ pendingBlobP.resolve();
154
+ this.pendingBlobIds.delete(blobId);
155
+ }
153
156
  this.blobIds.add(blobId);
154
157
  }
155
158
 
@@ -202,19 +205,83 @@ export class BlobManager {
202
205
  });
203
206
  }
204
207
 
205
- public snapshot(): ITree {
208
+ /**
209
+ * Generates data used for garbage collection. Each blob uploaded represents a node in the GC graph as it can be
210
+ * individually referenced by storing its handle in a referenced DDS. Returns the list of blob ids as GC nodes.
211
+ * @param fullGC - true to bypass optimizations and force full generation of GC data. BlobManager doesn't care
212
+ * about this for now because the data is a simple list of blob ids.
213
+ */
214
+ public getGCData(fullGC: boolean = false): IGarbageCollectionData {
215
+ const getGCNodePath = (blobId: string) => { return `/${BlobManager.basePath}/${blobId}`; };
216
+ const gcData: IGarbageCollectionData = { gcNodes: {} };
217
+ /**
218
+ * The node path is of the format `/_blobs/blobId`. This path must match the path of the blob handle returned
219
+ * by the createBlob API because blobs are marked referenced by storing these handles in a referenced DDS.
220
+ */
221
+ this.blobIds.forEach((blobId: string) => {
222
+ gcData.gcNodes[getGCNodePath(blobId)] = [];
223
+ });
224
+
225
+ /**
226
+ * For all blobs in the redirect table, the handle returned on creation is based off of the localId. So, these
227
+ * nodes can be referenced by storing the localId handle. When that happens, the corresponding storageId node
228
+ * must also be marked referenced. So, we add a route from the localId node to the storageId node.
229
+ * Note that because of de-duping, there can be multiple localIds that all redirect to the same storageId or
230
+ * a blob may be referenced via its storageId handle.
231
+ */
232
+ if (this.redirectTable !== undefined) {
233
+ for (const [localId, storageId] of this.redirectTable) {
234
+ // Add node for the localId and add a route to the storageId node. The storageId node will have been
235
+ // added above when adding nodes for this.blobIds.
236
+ gcData.gcNodes[getGCNodePath(localId)] = [getGCNodePath(storageId)];
237
+ }
238
+ }
239
+
240
+ return gcData;
241
+ }
242
+
243
+ /**
244
+ * When running GC in test mode, this is called to delete blobs that are unused.
245
+ * @param unusedRoutes - These are the blob node ids that are unused and should be deleted.
246
+ */
247
+ public deleteUnusedRoutes(unusedRoutes: string[]): void {
248
+ // The routes or blob node paths are in the same format as returned in getGCData - `/_blobs/blobId`.
249
+ for (const route of unusedRoutes) {
250
+ const pathParts = route.split("/");
251
+ assert(
252
+ pathParts.length === 3 && pathParts[1] === BlobManager.basePath,
253
+ 0x2d5 /* "Invalid blob node id in unused routes." */,
254
+ );
255
+ const blobId = pathParts[2];
256
+
257
+ // The unused blobId could be a localId. If so, remove it from the redirect table and continue. The
258
+ // corresponding storageId may still be used either directly or via other localIds.
259
+ if (this.redirectTable?.has(blobId)) {
260
+ this.redirectTable.delete(blobId);
261
+ continue;
262
+ }
263
+ this.blobIds.delete(blobId);
264
+ }
265
+ }
266
+
267
+ public summarize(): ISummaryTreeWithStats {
206
268
  // If we have a redirect table it means the container is about to transition to "Attaching" state, so we need
207
269
  // to return an actual snapshot containing all the real storage IDs we know about.
208
270
  const attachingOrAttached = !!this.redirectTable || this.runtime.attachState !== AttachState.Detached;
209
271
  const blobIds = attachingOrAttached ? this.blobIds : this.detachedBlobIds;
210
- const entries: ITreeEntry[] = [...blobIds].map((id) => new AttachmentTreeEntry(id, id));
272
+ const builder = new SummaryTreeBuilder();
273
+ blobIds.forEach((blobId) => {
274
+ builder.addAttachment(blobId);
275
+ });
276
+
211
277
  if (this.redirectTable && this.redirectTable.size > 0) {
212
- entries.push(new BlobTreeEntry(
278
+ builder.addBlob(
213
279
  BlobManager.redirectTableBlobName,
214
- JSON.stringify(Array.from(this.redirectTable.entries()))),
280
+ JSON.stringify(Array.from(this.redirectTable.entries())),
215
281
  );
216
282
  }
217
- return { entries };
283
+
284
+ return builder.getSummaryTree();
218
285
  }
219
286
 
220
287
  public setRedirectTable(table: Map<string, string>) {
@@ -9,6 +9,7 @@ import { IDeltaManager } from "@fluidframework/container-definitions";
9
9
  import {
10
10
  IDocumentMessage,
11
11
  ISequencedDocumentMessage,
12
+ MessageType,
12
13
  } from "@fluidframework/protocol-definitions";
13
14
  import { assert, performance } from "@fluidframework/common-utils";
14
15
 
@@ -17,17 +18,51 @@ import { assert, performance } from "@fluidframework/common-utils";
17
18
  */
18
19
  export const latencyThreshold = 5000;
19
20
 
21
+ // Phases in OpPerfTelemetry:
22
+ // 1. Op sits in a buffer in DeltaManager (DM) queue, then in outbound queue for some time.
23
+ // - Note: We do not differentiate these two today in telemetry, but first one is due to batches,
24
+ // second one might happen due to outbound queue being paused.
25
+ // 2. Op is sent to service and back.
26
+ // 3. Op sits in inbound queue.
27
+ // 4. Op is processed.
28
+ interface IOpPerfTelemetryProperties {
29
+ /** Measure time between (1) and (2) - Measure time outbound op is sitting in queue due to active batch */
30
+ durationOutboundQueue: number;
31
+ /** Measure time between (2) and (3) - Track how long op is sitting in inbound queue until it is processed */
32
+ durationInboundQueue: number;
33
+ /** Measure time between (3) and (4) - Time between DM's inbound "push" event until DM's "op" event */
34
+ durationInboundToProcessing: number;
35
+ /** Length of the DeltaManager's inbound queue at the time of the DM's inbound "push" event (3) */
36
+ lenghtInboundQueue: number;
37
+ }
38
+
39
+ /**
40
+ * Timings collected at various moments during the op processing.
41
+ */
42
+ interface IOpPerfTimings {
43
+ /** Starting time for (1) */
44
+ opStartTimeForLatencyStatistics: number;
45
+ /** Starting time for (2) */
46
+ opStartTimeSittingInboundQueue: number;
47
+ /** Starting time for (3) */
48
+ opStartTimeInboundPushEvent: number;
49
+ }
50
+
20
51
  class OpPerfTelemetry {
21
52
  private pongCount: number = 0;
22
53
  private pingLatency: number | undefined;
23
54
 
24
55
  // Collab window tracking. This is timestamp of %1000 message.
25
- private opSendTimeForLatencyStatisticsForMsnStatistics: number | undefined;
26
-
27
- // To track round trip time for every %1000 client message.
28
- private opSendTimeForLatencyStatistics: number | undefined;
56
+ private sequenceNumberForMsnTracking: number | undefined;
57
+ private msnTrackingTimestamp: number = 0;
58
+ // To track round trip time for every %500 client message.
29
59
  private clientSequenceNumberForLatencyStatistics: number | undefined;
30
60
 
61
+ private opProcessingTimes: Partial<IOpPerfTimings> = {};
62
+
63
+ // Performance Data to be reported for ops round trips and processing.
64
+ private opPerfData: Partial<IOpPerfTelemetryProperties> = {};
65
+
31
66
  private firstConnection = true;
32
67
  private connectionOpSeqNumber: number | undefined;
33
68
  private readonly bootTime = performance.now();
@@ -61,12 +96,49 @@ class OpPerfTelemetry {
61
96
  }
62
97
  });
63
98
  this.deltaManager.on("disconnect", () => {
64
- this.opSendTimeForLatencyStatisticsForMsnStatistics = undefined;
99
+ this.sequenceNumberForMsnTracking = undefined;
65
100
  this.clientSequenceNumberForLatencyStatistics = undefined;
101
+ this.opProcessingTimes = {};
102
+ this.opPerfData = {};
66
103
  this.connectionOpSeqNumber = undefined;
67
104
  this.firstConnection = false;
68
105
  });
69
106
 
107
+ this.deltaManager.outbound.on("push", (messages) => {
108
+ for (const msg of messages) {
109
+ if (msg.type === MessageType.Operation &&
110
+ this.clientSequenceNumberForLatencyStatistics === msg.clientSequenceNumber) {
111
+ assert(this.opProcessingTimes.opStartTimeSittingInboundQueue === undefined,
112
+ 0x2c8 /* "opStartTimeSittingInboundQueue should be undefined" */);
113
+ assert(this.opPerfData.durationInboundQueue === undefined,
114
+ 0x2c9 /* "durationInboundQueue should be undefined" */);
115
+ this.opProcessingTimes.opStartTimeSittingInboundQueue = Date.now();
116
+
117
+ assert(this.opPerfData.durationOutboundQueue === undefined,
118
+ 0x2ca /* "durationOutboundQueue should be undefined" */);
119
+
120
+ assert(this.opProcessingTimes.opStartTimeForLatencyStatistics !== undefined,
121
+ 0x2cb /* "opStartTimeForLatencyStatistics should be undefined" */);
122
+
123
+ this.opPerfData.durationOutboundQueue = this.opProcessingTimes.opStartTimeSittingInboundQueue
124
+ - this.opProcessingTimes.opStartTimeForLatencyStatistics;
125
+ }
126
+ }
127
+ });
128
+
129
+ this.deltaManager.inbound.on("push", (message: ISequencedDocumentMessage) => {
130
+ if (this.clientId === message.clientId &&
131
+ message.type === MessageType.Operation &&
132
+ this.clientSequenceNumberForLatencyStatistics === message.clientSequenceNumber &&
133
+ this.opProcessingTimes.opStartTimeSittingInboundQueue !== undefined) {
134
+ this.opProcessingTimes.opStartTimeInboundPushEvent = Date.now();
135
+ this.opPerfData.durationInboundQueue = this.opProcessingTimes.opStartTimeInboundPushEvent
136
+ - this.opProcessingTimes.opStartTimeSittingInboundQueue;
137
+ this.opProcessingTimes.opStartTimeSittingInboundQueue = undefined;
138
+ this.opPerfData.lenghtInboundQueue = this.deltaManager.inbound.length;
139
+ }
140
+ });
141
+
70
142
  this.deltaManager.inbound.on("idle", (count: number, duration: number) => {
71
143
  // Do not want to log zero for sure.
72
144
  // We are more interested in aggregates, so logging only if we are processing some number of ops
@@ -113,8 +185,13 @@ class OpPerfTelemetry {
113
185
 
114
186
  private beforeOpSubmit(message: IDocumentMessage) {
115
187
  // start with first client op and measure latency every 500 client ops
116
- if (this.clientSequenceNumberForLatencyStatistics === undefined && message.clientSequenceNumber % 500 === 1) {
117
- this.opSendTimeForLatencyStatistics = Date.now();
188
+ if (this.clientSequenceNumberForLatencyStatistics === undefined &&
189
+ message.clientSequenceNumber % 500 === 1) {
190
+ assert(this.opProcessingTimes.opStartTimeSittingInboundQueue === undefined,
191
+ 0x2cc /* "OpTimeSittingInboundQueue should be undefined" */);
192
+ assert(this.opPerfData.durationInboundQueue === undefined,
193
+ 0x2cd /* "durationInboundQueue should be undefined" */);
194
+ this.opProcessingTimes.opStartTimeForLatencyStatistics = Date.now();
118
195
  this.clientSequenceNumberForLatencyStatistics = message.clientSequenceNumber;
119
196
  }
120
197
  }
@@ -127,24 +204,35 @@ class OpPerfTelemetry {
127
204
  }
128
205
 
129
206
  // Record collab window max size after every 1000th op.
130
- if (sequenceNumber % 1000 === 0) {
131
- if (this.opSendTimeForLatencyStatisticsForMsnStatistics !== undefined) {
132
- this.logger.sendPerformanceEvent({
133
- eventName: "MsnStatistics",
134
- sequenceNumber,
135
- msnDistance: this.deltaManager.lastSequenceNumber - this.deltaManager.minimumSequenceNumber,
136
- duration: message.timestamp - this.opSendTimeForLatencyStatisticsForMsnStatistics,
137
- });
138
- }
139
- this.opSendTimeForLatencyStatisticsForMsnStatistics = message.timestamp;
207
+ if (this.sequenceNumberForMsnTracking === undefined && sequenceNumber % 1000 === 0) {
208
+ this.sequenceNumberForMsnTracking = sequenceNumber;
209
+ this.msnTrackingTimestamp = message.timestamp;
210
+ }
211
+ if (this.sequenceNumberForMsnTracking !== undefined &&
212
+ message.minimumSequenceNumber >= this.sequenceNumberForMsnTracking) {
213
+ assert(this.msnTrackingTimestamp !== undefined,
214
+ 0x2ce /* "msnTrackingTimestamp should not be undefined" */);
215
+ this.logger.sendPerformanceEvent({
216
+ eventName: "MsnStatistics",
217
+ sequenceNumber,
218
+ msnDistance: sequenceNumber - this.sequenceNumberForMsnTracking,
219
+ duration: message.timestamp - this.msnTrackingTimestamp,
220
+ });
221
+ this.sequenceNumberForMsnTracking = undefined;
140
222
  }
141
223
 
142
224
  if (this.clientId === message.clientId &&
143
225
  this.clientSequenceNumberForLatencyStatistics === message.clientSequenceNumber) {
144
- assert(this.opSendTimeForLatencyStatistics !== undefined,
226
+ assert(this.opProcessingTimes.opStartTimeForLatencyStatistics !== undefined,
145
227
  0x120 /* "Undefined latency statistics (op send time)" */);
228
+ const currentTime = Date.now();
229
+
230
+ if (this.opProcessingTimes.opStartTimeInboundPushEvent !== undefined) {
231
+ this.opPerfData.durationInboundToProcessing = currentTime
232
+ - this.opProcessingTimes.opStartTimeInboundPushEvent;
233
+ }
146
234
 
147
- const duration = Date.now() - this.opSendTimeForLatencyStatistics;
235
+ const duration = currentTime - this.opProcessingTimes.opStartTimeForLatencyStatistics;
148
236
 
149
237
  // One of the core expectations for Fluid service is to be fast.
150
238
  // When it's not the case, we want to learn about it and be able to investigate, so
@@ -162,8 +250,11 @@ class OpPerfTelemetry {
162
250
  duration,
163
251
  category,
164
252
  pingLatency: this.pingLatency,
253
+ msnDistance: this.deltaManager.lastSequenceNumber - this.deltaManager.minimumSequenceNumber,
254
+ ...this.opPerfData,
165
255
  });
166
256
  this.clientSequenceNumberForLatencyStatistics = undefined;
257
+ this.opPerfData = {};
167
258
  }
168
259
  }
169
260
  }