@fluid-tools/fetch-tool 2.0.0-dev-rc.2.0.0.246488 → 2.0.0-dev-rc.3.0.0.253463

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 (58) hide show
  1. package/dist/fluidAnalyzeMessages.js +22 -19
  2. package/dist/fluidAnalyzeMessages.js.map +1 -1
  3. package/dist/fluidFetch.js +18 -18
  4. package/dist/fluidFetch.js.map +1 -1
  5. package/dist/fluidFetchInit.d.ts +1 -1
  6. package/dist/fluidFetchInit.d.ts.map +1 -1
  7. package/dist/fluidFetchInit.js +15 -15
  8. package/dist/fluidFetchInit.js.map +1 -1
  9. package/dist/fluidFetchMessages.d.ts +1 -1
  10. package/dist/fluidFetchMessages.d.ts.map +1 -1
  11. package/dist/fluidFetchMessages.js +13 -13
  12. package/dist/fluidFetchMessages.js.map +1 -1
  13. package/dist/fluidFetchSharePoint.d.ts +1 -1
  14. package/dist/fluidFetchSharePoint.d.ts.map +1 -1
  15. package/dist/fluidFetchSharePoint.js +6 -6
  16. package/dist/fluidFetchSharePoint.js.map +1 -1
  17. package/dist/fluidFetchSnapshot.d.ts +1 -1
  18. package/dist/fluidFetchSnapshot.d.ts.map +1 -1
  19. package/dist/fluidFetchSnapshot.js +20 -20
  20. package/dist/fluidFetchSnapshot.js.map +1 -1
  21. package/dist/package.json +3 -0
  22. package/lib/fluidAnalyzeMessages.d.ts +8 -0
  23. package/lib/fluidAnalyzeMessages.d.ts.map +1 -0
  24. package/lib/fluidAnalyzeMessages.js +629 -0
  25. package/lib/fluidAnalyzeMessages.js.map +1 -0
  26. package/lib/fluidFetch.d.ts +6 -0
  27. package/lib/fluidFetch.d.ts.map +1 -0
  28. package/lib/fluidFetch.js +120 -0
  29. package/lib/fluidFetch.js.map +1 -0
  30. package/lib/fluidFetchArgs.d.ts +31 -0
  31. package/lib/fluidFetchArgs.d.ts.map +1 -0
  32. package/lib/fluidFetchArgs.js +192 -0
  33. package/lib/fluidFetchArgs.js.map +1 -0
  34. package/lib/fluidFetchInit.d.ts +8 -0
  35. package/lib/fluidFetchInit.d.ts.map +1 -0
  36. package/lib/fluidFetchInit.js +117 -0
  37. package/lib/fluidFetchInit.js.map +1 -0
  38. package/lib/fluidFetchMessages.d.ts +7 -0
  39. package/lib/fluidFetchMessages.d.ts.map +1 -0
  40. package/lib/fluidFetchMessages.js +225 -0
  41. package/lib/fluidFetchMessages.js.map +1 -0
  42. package/lib/fluidFetchSharePoint.d.ts +9 -0
  43. package/lib/fluidFetchSharePoint.d.ts.map +1 -0
  44. package/lib/fluidFetchSharePoint.js +100 -0
  45. package/lib/fluidFetchSharePoint.js.map +1 -0
  46. package/lib/fluidFetchSnapshot.d.ts +7 -0
  47. package/lib/fluidFetchSnapshot.d.ts.map +1 -0
  48. package/lib/fluidFetchSnapshot.js +261 -0
  49. package/lib/fluidFetchSnapshot.js.map +1 -0
  50. package/package.json +26 -26
  51. package/src/fluidAnalyzeMessages.ts +13 -10
  52. package/src/fluidFetch.ts +8 -6
  53. package/src/fluidFetchInit.ts +13 -9
  54. package/src/fluidFetchMessages.ts +8 -6
  55. package/src/fluidFetchSharePoint.ts +10 -8
  56. package/src/fluidFetchSnapshot.ts +7 -4
  57. package/tsconfig.cjs.json +7 -0
  58. package/tsconfig.json +2 -5
@@ -0,0 +1,629 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { ContainerMessageType, unpackRuntimeMessage, } from "@fluidframework/container-runtime/internal";
6
+ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
7
+ import { DataStoreMessageType } from "@fluidframework/datastore/internal";
8
+ import { MessageType, TreeEntry, } from "@fluidframework/protocol-definitions";
9
+ const noClientName = "No Client";
10
+ const objectTypePrefix = "https://graph.microsoft.com/types/";
11
+ function incr(map, key, size, count = 1) {
12
+ const value = map.get(key);
13
+ if (value === undefined) {
14
+ map.set(key, [count, size]);
15
+ }
16
+ else {
17
+ value[0] += count;
18
+ value[1] += size;
19
+ map.set(key, value);
20
+ }
21
+ }
22
+ /**
23
+ * Helper class to track session statistics
24
+ */
25
+ class ActiveSession {
26
+ static create(email, message) {
27
+ return new ActiveSession(email, message);
28
+ }
29
+ constructor(email, startMessage) {
30
+ this.email = email;
31
+ this.startMessage = startMessage;
32
+ this.opCount = 0;
33
+ }
34
+ reportOp(timestamp) {
35
+ this.opCount++;
36
+ }
37
+ leave(timestamp) {
38
+ return {
39
+ opCount: this.opCount,
40
+ email: this.email,
41
+ startSeq: this.startMessage.sequenceNumber,
42
+ duration: timestamp - this.startMessage.timestamp,
43
+ };
44
+ }
45
+ }
46
+ // Format a number separating 3 digits by comma
47
+ export const formatNumber = (num) =>
48
+ // eslint-disable-next-line unicorn/no-unsafe-regex
49
+ num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
50
+ function dumpStats(map, props) {
51
+ const fieldSizes = [10, 14];
52
+ const nameLength = 72;
53
+ const fieldsLength = fieldSizes[0] + fieldSizes[1] + 1;
54
+ let headers = props.headers;
55
+ let recordsToShow = props.lines ?? 10;
56
+ if (map.size !== recordsToShow && props.removeTotals === undefined && recordsToShow > 1) {
57
+ recordsToShow--;
58
+ }
59
+ let sorted;
60
+ const sortIndex = props.orderByFirstColumn === true ? 0 : 1;
61
+ let add;
62
+ if (props.reverseSort !== undefined) {
63
+ sorted = [...map.entries()].sort((a, b) => a[1][sortIndex] - b[1][sortIndex]);
64
+ add = "↑";
65
+ }
66
+ else {
67
+ sorted = [...map.entries()].sort((a, b) => b[1][sortIndex] - a[1][sortIndex]);
68
+ add = "↓";
69
+ }
70
+ headers[sortIndex] = `${headers[sortIndex]} ${add}`;
71
+ if (props.reverseColumnsInUI !== undefined) {
72
+ headers = [headers[1], headers[0]];
73
+ const sorted2 = [];
74
+ for (const [name, [count, size]] of sorted) {
75
+ sorted2.push([name, [size, count]]);
76
+ }
77
+ sorted = sorted2;
78
+ }
79
+ let totalCount = 0;
80
+ let sizeTotal = 0;
81
+ props.title = `${props.title} (${sorted.length})`;
82
+ const header0 = headers[0].padStart(fieldSizes[0]);
83
+ let overflow = header0.length - fieldSizes[0];
84
+ console.log(`\n\n${props.title.padEnd(nameLength)} │ ${header0} ${headers[1].padStart(fieldSizes[1] - overflow)}`);
85
+ console.log(`${"─".repeat(nameLength + 1)}┼${"─".repeat(fieldsLength + 1)}`);
86
+ let index = 0;
87
+ let allOtherCount = 0;
88
+ let allOtherSize = 0;
89
+ for (const [name, [count, size]] of sorted) {
90
+ index++;
91
+ totalCount += count;
92
+ sizeTotal += size;
93
+ if (index <= recordsToShow) {
94
+ const item = name.padEnd(nameLength);
95
+ overflow = item.length - nameLength;
96
+ const col1 = formatNumber(count).padStart(fieldSizes[0] - overflow);
97
+ overflow += col1.length - fieldSizes[0];
98
+ const col2 = formatNumber(size).padStart(fieldSizes[1] - overflow);
99
+ console.log(`${item} │ ${col1} ${col2}`);
100
+ }
101
+ else {
102
+ allOtherCount += count;
103
+ allOtherSize += size;
104
+ }
105
+ }
106
+ if (props.removeTotals === undefined) {
107
+ if (allOtherCount || allOtherSize) {
108
+ console.log(`${`All Others (${sorted.length - recordsToShow})`.padEnd(nameLength)} │ ${formatNumber(allOtherCount).padStart(fieldSizes[0])} ${formatNumber(allOtherSize).padStart(fieldSizes[1])}`);
109
+ }
110
+ console.log(`${"─".repeat(nameLength + 1)}┼${"─".repeat(fieldsLength + 1)}`);
111
+ console.log(`${"Total".padEnd(nameLength)} │ ${formatNumber(totalCount).padStart(fieldSizes[0])} ${formatNumber(sizeTotal).padStart(fieldSizes[1])}`);
112
+ }
113
+ }
114
+ const getObjectId = (dataStoreId, id) => `[${dataStoreId}]/${id}`;
115
+ /**
116
+ * Analyzer for sessions
117
+ */
118
+ class SessionAnalyzer {
119
+ constructor() {
120
+ this.sessionsInProgress = new Map();
121
+ this.sessions = new Map();
122
+ this.users = new Map();
123
+ this.first = true;
124
+ }
125
+ processOp(message, msgSize, skipMessage) {
126
+ if (this.first) {
127
+ this.first = false;
128
+ // Start of the road.
129
+ const noNameSession = ActiveSession.create(noClientName, message);
130
+ this.sessionsInProgress.set(noClientName, noNameSession);
131
+ }
132
+ const session = processQuorumMessages(message, skipMessage, this.sessionsInProgress, this.sessions, this.users);
133
+ if (!skipMessage && session) {
134
+ session.reportOp(message.timestamp);
135
+ }
136
+ }
137
+ reportAnalyzes(lastOp) {
138
+ // Close any open sessions
139
+ reportOpenSessions(lastOp.timestamp, this.sessionsInProgress, this.sessions, this.users);
140
+ dumpStats(this.users, {
141
+ title: "Users",
142
+ headers: ["Sessions", "Op count"],
143
+ reverseColumnsInUI: true,
144
+ lines: 6,
145
+ });
146
+ dumpStats(this.sessions, {
147
+ title: "Sessions",
148
+ headers: ["Duration(s)", "Op count"],
149
+ reverseColumnsInUI: true,
150
+ lines: 6,
151
+ });
152
+ dumpStats(this.sessions, {
153
+ title: "Sessions",
154
+ headers: ["Duration(s)", "Op count"],
155
+ orderByFirstColumn: true,
156
+ reverseColumnsInUI: true,
157
+ removeTotals: true,
158
+ lines: 5,
159
+ });
160
+ }
161
+ }
162
+ /**
163
+ * Analyzer for data structures
164
+ */
165
+ class DataStructureAnalyzer {
166
+ constructor() {
167
+ this.messageTypeStats = new Map();
168
+ this.dataType = new Map();
169
+ this.dataTypeStats = new Map();
170
+ this.objectStats = new Map();
171
+ // eslint-disable-next-line @typescript-eslint/member-delimiter-style
172
+ this.chunkMap = new Map();
173
+ }
174
+ processOp(message, msgSize, skipMessage) {
175
+ if (!skipMessage) {
176
+ processOp(message, this.dataType, this.objectStats, msgSize, this.dataTypeStats, this.messageTypeStats, this.chunkMap);
177
+ }
178
+ }
179
+ reportAnalyzes(lastOp) {
180
+ dumpStats(this.messageTypeStats, {
181
+ title: "Message Type",
182
+ headers: ["Op count", "Bytes"],
183
+ lines: 20,
184
+ });
185
+ dumpStats(calcChannelStats(this.dataType, this.objectStats), {
186
+ title: "Channel name",
187
+ headers: ["Op count", "Bytes"],
188
+ lines: 7,
189
+ });
190
+ /*
191
+ dumpStats(this.dataTypeStats, {
192
+ title: "Channel type",
193
+ headers: ["Op count", "Bytes"],
194
+ });
195
+ */
196
+ }
197
+ }
198
+ /**
199
+ * Helper class to report if we filtered out any messages.
200
+ */
201
+ class FilteredMessageAnalyzer {
202
+ constructor() {
203
+ this.sizeTotal = 0;
204
+ this.opsTotal = 0;
205
+ this.sizeFiltered = 0;
206
+ this.opsFiltered = 0;
207
+ this.filtered = false;
208
+ }
209
+ processOp(message, msgSize, skipMessage) {
210
+ this.sizeTotal += msgSize;
211
+ this.opsTotal++;
212
+ if (!skipMessage) {
213
+ this.sizeFiltered += msgSize;
214
+ this.opsFiltered++;
215
+ }
216
+ else {
217
+ this.filtered = true;
218
+ }
219
+ }
220
+ reportAnalyzes(lastOp) {
221
+ if (this.filtered) {
222
+ console.log(`\nData is filtered according to --filter:messageType argument(s):\nOp size: ${this.sizeFiltered} / ${this.sizeTotal}\nOp count ${this.opsFiltered} / ${this.opsTotal}`);
223
+ }
224
+ if (this.opsTotal === 0) {
225
+ console.error("No ops were found");
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Helper class to find places where we generated too many ops
231
+ */
232
+ class MessageDensityAnalyzer {
233
+ constructor() {
234
+ this.opChunk = 1000;
235
+ this.opLimit = 1;
236
+ this.size = 0;
237
+ this.timeStart = 0;
238
+ this.doctimerStart = 0;
239
+ this.ranges = new Map();
240
+ }
241
+ processOp(message, msgSize, skipMessage) {
242
+ if (message.sequenceNumber >= this.opLimit) {
243
+ if (message.sequenceNumber !== 1) {
244
+ const timeDiff = durationFromTime(message.timestamp - this.timeStart);
245
+ const opsString = `ops = [${this.opLimit - this.opChunk}, ${this.opLimit - 1}]`.padEnd(26);
246
+ const timeString = `time = [${durationFromTime(this.timeStart - this.doctimerStart)}, ${durationFromTime(message.timestamp - this.doctimerStart)}]`;
247
+ this.ranges.set(`${opsString} ${timeString}`, [timeDiff, this.size]);
248
+ }
249
+ else {
250
+ this.doctimerStart = message.timestamp;
251
+ }
252
+ this.opLimit += this.opChunk;
253
+ this.size = 0;
254
+ this.timeStart = message.timestamp;
255
+ }
256
+ if (!skipMessage) {
257
+ this.size += msgSize;
258
+ }
259
+ }
260
+ reportAnalyzes(lastOp) {
261
+ dumpStats(this.ranges, {
262
+ title: "Fastest 1000 op ranges",
263
+ headers: ["Duration(s)", "Bytes"],
264
+ orderByFirstColumn: true,
265
+ reverseSort: true,
266
+ removeTotals: true,
267
+ lines: 3,
268
+ });
269
+ }
270
+ }
271
+ /**
272
+ * Helper class to analyze collab window size
273
+ */
274
+ class CollabWindowSizeAnalyzer {
275
+ constructor() {
276
+ this.maxCollabWindow = 0;
277
+ this.opSeq = 0;
278
+ }
279
+ processOp(message, msgSize, skipMessage) {
280
+ const value = message.sequenceNumber - message.minimumSequenceNumber;
281
+ if (value > this.maxCollabWindow) {
282
+ this.maxCollabWindow = value;
283
+ this.opSeq = message.sequenceNumber;
284
+ }
285
+ }
286
+ reportAnalyzes(lastOp) {
287
+ console.log(`\nMaximum collab window size: ${this.maxCollabWindow}, seq# ${this.opSeq}`);
288
+ }
289
+ }
290
+ /**
291
+ * Helper class to analyze frequency of summaries
292
+ */
293
+ class SummaryAnalyzer {
294
+ constructor() {
295
+ this.lastSummaryOp = 0;
296
+ this.maxDistance = 0;
297
+ this.maxSeq = 0;
298
+ this.minDistance = Number.MAX_SAFE_INTEGER;
299
+ this.minSeq = 0;
300
+ this.maxResponse = 0;
301
+ this.maxResponseSeq = 0;
302
+ }
303
+ processOp(message, msgSize, skipMessage) {
304
+ if (message.type === MessageType.SummaryAck) {
305
+ const distance = message.sequenceNumber - this.lastSummaryOp - 1;
306
+ if (this.maxDistance < distance) {
307
+ this.maxDistance = distance;
308
+ this.maxSeq = message.sequenceNumber;
309
+ }
310
+ if (this.minDistance > distance) {
311
+ this.minDistance = distance;
312
+ this.minSeq = message.sequenceNumber;
313
+ }
314
+ this.lastSummaryOp = message.sequenceNumber;
315
+ }
316
+ if (message.type === MessageType.SummaryAck || message.type === MessageType.SummaryNack) {
317
+ const contents = message.contents
318
+ .summaryProposal;
319
+ const distance = message.sequenceNumber - contents.summarySequenceNumber;
320
+ if (distance > this.maxResponse) {
321
+ this.maxResponse = distance;
322
+ this.maxResponseSeq = message.sequenceNumber;
323
+ }
324
+ }
325
+ }
326
+ reportAnalyzes(lastOp) {
327
+ const distance = lastOp.sequenceNumber - this.lastSummaryOp;
328
+ if (this.maxDistance < distance) {
329
+ this.maxDistance = distance;
330
+ this.maxSeq = lastOp.sequenceNumber + 1;
331
+ }
332
+ console.log("");
333
+ if (this.minDistance === Number.MAX_SAFE_INTEGER) {
334
+ console.log("No summaries found in this document");
335
+ }
336
+ else {
337
+ console.log(`Maximum distance between summaries: ${this.maxDistance}, seq# ${this.maxSeq}`);
338
+ console.log(`Maximum server response for summary: ${this.maxResponse}, seq# ${this.maxResponseSeq}`);
339
+ console.log(`Minimum distance between summaries: ${this.minDistance}, seq# ${this.minSeq}`);
340
+ }
341
+ }
342
+ }
343
+ /**
344
+ * Helper class to dump messages to console
345
+ */
346
+ class MessageDumper {
347
+ processOp(message, msgSize, skipMessage) {
348
+ if (!skipMessage) {
349
+ console.log(JSON.stringify(message, undefined, 2));
350
+ }
351
+ }
352
+ reportAnalyzes(lastOp) { }
353
+ }
354
+ export async function printMessageStats(generator, // AsyncGenerator<ISequencedDocumentMessage[]>,
355
+ dumpMessageStats, dumpMessages, messageTypeFilter = new Set()) {
356
+ let lastMessage;
357
+ const analyzers = [
358
+ new FilteredMessageAnalyzer(),
359
+ new SessionAnalyzer(),
360
+ new DataStructureAnalyzer(),
361
+ new MessageDensityAnalyzer(),
362
+ new CollabWindowSizeAnalyzer(),
363
+ new SummaryAnalyzer(),
364
+ ];
365
+ if (dumpMessages) {
366
+ analyzers.push(new MessageDumper());
367
+ }
368
+ for await (const messages of generator) {
369
+ for (const message of messages) {
370
+ const msgSize = JSON.stringify(message).length;
371
+ lastMessage = message;
372
+ const skipMessage = messageTypeFilter.size !== 0 && !messageTypeFilter.has(message.type);
373
+ for (const analyzer of analyzers) {
374
+ analyzer.processOp(message, msgSize, skipMessage);
375
+ }
376
+ }
377
+ }
378
+ if (lastMessage !== undefined) {
379
+ if (dumpMessageStats) {
380
+ for (const analyzer of analyzers) {
381
+ analyzer.reportAnalyzes(lastMessage);
382
+ }
383
+ }
384
+ else {
385
+ // Warn about filtered messages
386
+ analyzers[0].reportAnalyzes(lastMessage);
387
+ }
388
+ }
389
+ console.log("");
390
+ }
391
+ function processOp(runtimeMessage, dataType, objectStats, msgSize, dataTypeStats, messageTypeStats, chunkMap) {
392
+ let type = runtimeMessage.type;
393
+ let recorded = false;
394
+ let totalMsgSize = msgSize;
395
+ let opCount = 1;
396
+ if (unpackRuntimeMessage(runtimeMessage)) {
397
+ const messageType = runtimeMessage.type;
398
+ switch (messageType) {
399
+ case ContainerMessageType.Attach: {
400
+ const attachMessage = runtimeMessage.contents;
401
+ processDataStoreAttachOp(attachMessage, dataType);
402
+ break;
403
+ }
404
+ // skip for now because these ops do not have contents
405
+ case ContainerMessageType.BlobAttach: {
406
+ break;
407
+ }
408
+ // The default method to count stats should be used for GC messages.
409
+ case ContainerMessageType.GC: {
410
+ break;
411
+ }
412
+ case ContainerMessageType.DocumentSchemaChange: {
413
+ break;
414
+ }
415
+ case ContainerMessageType.ChunkedOp: {
416
+ const chunk = runtimeMessage.contents;
417
+ // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
418
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
419
+ if (!chunkMap.has(runtimeMessage.clientId)) {
420
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
421
+ chunkMap.set(runtimeMessage.clientId, {
422
+ chunks: new Array(chunk.totalChunks),
423
+ totalSize: 0,
424
+ });
425
+ }
426
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
427
+ const value = chunkMap.get(runtimeMessage.clientId);
428
+ assert(value !== undefined, 0x2b8 /* "Chunk should be set in map" */);
429
+ const chunks = value.chunks;
430
+ const chunkIndex = chunk.chunkId - 1;
431
+ if (chunks[chunkIndex] !== undefined) {
432
+ throw new Error("Chunk already assigned");
433
+ }
434
+ chunks[chunkIndex] = chunk.contents;
435
+ value.totalSize += msgSize;
436
+ if (chunk.chunkId === chunk.totalChunks) {
437
+ opCount = chunk.totalChunks; // 1 op for each chunk.
438
+ const patchedMessage = Object.create(runtimeMessage);
439
+ patchedMessage.contents = chunks.join("");
440
+ type = chunk.originalType;
441
+ patchedMessage.type = type;
442
+ totalMsgSize = value.totalSize;
443
+ chunkMap.delete(patchedMessage.clientId);
444
+ }
445
+ else {
446
+ return;
447
+ }
448
+ // eslint-disable-next-line no-fallthrough
449
+ }
450
+ case ContainerMessageType.IdAllocation:
451
+ case ContainerMessageType.FluidDataStoreOp:
452
+ case ContainerMessageType.Alias:
453
+ case ContainerMessageType.Rejoin: {
454
+ let envelope = runtimeMessage.contents;
455
+ // TODO: Legacy?
456
+ if (envelope !== undefined && typeof envelope === "string") {
457
+ envelope = JSON.parse(envelope);
458
+ }
459
+ const innerContent = envelope.contents;
460
+ const address = envelope.address;
461
+ type = `${type}/${innerContent.type}`;
462
+ switch (innerContent.type) {
463
+ case DataStoreMessageType.Attach: {
464
+ const attachMessage = innerContent.content;
465
+ let objectType = attachMessage.type;
466
+ if (objectType.startsWith(objectTypePrefix)) {
467
+ objectType = objectType.substring(objectTypePrefix.length);
468
+ }
469
+ dataType.set(getObjectId(address, attachMessage.id), objectType);
470
+ break;
471
+ }
472
+ case DataStoreMessageType.ChannelOp:
473
+ default: {
474
+ const innerEnvelope = innerContent.content;
475
+ const innerContent2 = innerEnvelope.contents;
476
+ const objectId = getObjectId(address, innerEnvelope.address);
477
+ incr(objectStats, objectId, totalMsgSize, opCount);
478
+ let objectType = dataType.get(objectId);
479
+ if (objectType === undefined) {
480
+ // Somehow we do not have data...
481
+ dataType.set(objectId, objectId);
482
+ objectType = objectId;
483
+ }
484
+ incr(dataTypeStats, objectType, totalMsgSize, opCount);
485
+ recorded = true;
486
+ let subType = innerContent2.type;
487
+ if (innerContent2.type === "set" &&
488
+ typeof innerContent2.value === "object" &&
489
+ innerContent2.value !== null) {
490
+ type = `${type}/${subType}`;
491
+ subType = innerContent2.value.type;
492
+ }
493
+ else if (objectType === "mergeTree" && subType !== undefined) {
494
+ const types = ["insert", "remove", "annotate", "group"];
495
+ if (types[subType] !== undefined) {
496
+ subType = types[subType];
497
+ }
498
+ }
499
+ if (subType !== undefined) {
500
+ type = `${type}/${subType}`;
501
+ }
502
+ type = `${type} (${objectType})`;
503
+ }
504
+ }
505
+ break;
506
+ }
507
+ default:
508
+ unreachableCase(messageType, "Message type not recognized!");
509
+ }
510
+ }
511
+ incr(messageTypeStats, type, totalMsgSize, opCount);
512
+ if (!recorded) {
513
+ // const objectId = `${type} (system)`;
514
+ const objectId = `(system messages)`;
515
+ const objectType = objectId;
516
+ if (dataType.get(objectId) === undefined) {
517
+ dataType.set(objectId, objectId);
518
+ }
519
+ incr(objectStats, objectId, totalMsgSize, opCount);
520
+ incr(dataTypeStats, objectType, totalMsgSize, opCount);
521
+ }
522
+ }
523
+ function processDataStoreAttachOp(attachMessage, dataType) {
524
+ // dataType.set(getObjectId(attachMessage.id), attachMessage.type);
525
+ // That's data store, and it brings a bunch of data structures.
526
+ // Let's try to crack it.
527
+ const parsedAttachMessage = typeof attachMessage === "string" ? JSON.parse(attachMessage) : attachMessage;
528
+ for (const entry of parsedAttachMessage.snapshot.entries) {
529
+ if (entry.type === TreeEntry.Tree) {
530
+ for (const entry2 of entry.value.entries) {
531
+ if (entry2.path === ".attributes" && entry2.type === TreeEntry.Blob) {
532
+ const attrib = JSON.parse(entry2.value.contents);
533
+ let objectType = attrib.type;
534
+ if (objectType.startsWith(objectTypePrefix)) {
535
+ objectType = objectType.substring(objectTypePrefix.length);
536
+ }
537
+ dataType.set(getObjectId(parsedAttachMessage.id, entry.path), objectType);
538
+ }
539
+ }
540
+ }
541
+ }
542
+ }
543
+ function reportOpenSessions(lastOpTimestamp, sessionsInProgress, sessions, users) {
544
+ const activeSessions = new Map();
545
+ for (const [clientId, ses] of sessionsInProgress) {
546
+ const sessionInfo = ses.leave(lastOpTimestamp);
547
+ if (clientId !== noClientName) {
548
+ const sessionName = `${clientId} (${sessionInfo.email})`;
549
+ const sessionPayload = [
550
+ durationFromTime(sessionInfo.duration),
551
+ sessionInfo.opCount,
552
+ ];
553
+ sessions.set(sessionName, sessionPayload);
554
+ activeSessions.set(sessionName, sessionPayload);
555
+ }
556
+ else {
557
+ sessions.set(`Full file lifespan (noClient messages)`, [
558
+ durationFromTime(sessionInfo.duration),
559
+ sessionInfo.opCount,
560
+ ]);
561
+ }
562
+ incr(users, sessionInfo.email, sessionInfo.opCount);
563
+ }
564
+ if (activeSessions.size > 0) {
565
+ dumpStats(activeSessions, {
566
+ title: "Active sessions",
567
+ headers: ["Duration", "Op count"],
568
+ lines: 6,
569
+ orderByFirstColumn: true,
570
+ removeTotals: true,
571
+ });
572
+ }
573
+ }
574
+ function calcChannelStats(dataType, objectStats) {
575
+ const channelStats = new Map();
576
+ for (const [objectId, type] of dataType) {
577
+ let value = objectStats.get(objectId);
578
+ if (value === undefined) {
579
+ value = [0, 0];
580
+ }
581
+ if (type === objectId) {
582
+ channelStats.set(`${objectId}`, value);
583
+ }
584
+ else {
585
+ channelStats.set(`${objectId} (${type})`, value);
586
+ }
587
+ }
588
+ return channelStats;
589
+ }
590
+ function processQuorumMessages(message, skipMessage, sessionsInProgress, sessions, users) {
591
+ let session;
592
+ const dataString = message.data;
593
+ if (message.type === "join") {
594
+ const data = JSON.parse(dataString);
595
+ session = ActiveSession.create(data.detail.user.id, message);
596
+ sessionsInProgress.set(data.clientId, session);
597
+ }
598
+ else if (message.type === "leave") {
599
+ const clientId = JSON.parse(dataString);
600
+ session = sessionsInProgress.get(clientId);
601
+ sessionsInProgress.delete(clientId);
602
+ assert(!!session, 0x1b7 /* "Bad session state for processing quorum messages" */);
603
+ if (session !== undefined) {
604
+ if (!skipMessage) {
605
+ session.reportOp(message.timestamp);
606
+ }
607
+ const sessionInfo = session.leave(message.timestamp);
608
+ sessions.set(`${clientId} (${sessionInfo.email})`, [
609
+ durationFromTime(sessionInfo.duration),
610
+ sessionInfo.opCount,
611
+ ]);
612
+ incr(users, sessionInfo.email, sessionInfo.opCount);
613
+ session = undefined; // Do not record it second time
614
+ }
615
+ }
616
+ else {
617
+ // message.clientId can be null
618
+ // TODO: Verify whether this should be able to handle server-generated ops (with null clientId)
619
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
620
+ session = sessionsInProgress.get(message.clientId);
621
+ if (session === undefined) {
622
+ session = sessionsInProgress.get(noClientName);
623
+ assert(!!session, 0x1b8 /* "Bad session state for processing quorum messages" */);
624
+ }
625
+ }
626
+ return session;
627
+ }
628
+ const durationFromTime = (time) => Math.floor(time / 1000);
629
+ //# sourceMappingURL=fluidAnalyzeMessages.js.map