@fluid-experimental/tree 2.117.0 → 3.0.0

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 (167) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/README.md +52 -48
  3. package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -6
  4. package/api-extractor/api-extractor-report.json +9 -0
  5. package/dist/ChangeCompression.js +4 -3
  6. package/dist/ChangeCompression.js.map +1 -1
  7. package/dist/ChangeTypes.d.ts.map +1 -1
  8. package/dist/Checkout.js +28 -1
  9. package/dist/Checkout.js.map +1 -1
  10. package/dist/Common.js +27 -27
  11. package/dist/Common.js.map +1 -1
  12. package/dist/EditLog.d.ts.map +1 -1
  13. package/dist/EditLog.js +15 -11
  14. package/dist/EditLog.js.map +1 -1
  15. package/dist/EditUtilities.js +18 -18
  16. package/dist/EditUtilities.js.map +1 -1
  17. package/dist/Forest.js +12 -3
  18. package/dist/Forest.js.map +1 -1
  19. package/dist/HistoryEditFactory.js +2 -2
  20. package/dist/HistoryEditFactory.js.map +1 -1
  21. package/dist/IdConversion.js +4 -5
  22. package/dist/IdConversion.js.map +1 -1
  23. package/dist/Identifiers.js +1 -2
  24. package/dist/Identifiers.js.map +1 -1
  25. package/dist/LazyCheckout.js +1 -0
  26. package/dist/LazyCheckout.js.map +1 -1
  27. package/dist/LogViewer.js +50 -16
  28. package/dist/LogViewer.js.map +1 -1
  29. package/dist/MergeHealth.d.ts.map +1 -1
  30. package/dist/MergeHealth.js +123 -124
  31. package/dist/MergeHealth.js.map +1 -1
  32. package/dist/NodeIdUtilities.js +4 -5
  33. package/dist/NodeIdUtilities.js.map +1 -1
  34. package/dist/PayloadUtilities.js +1 -2
  35. package/dist/PayloadUtilities.js.map +1 -1
  36. package/dist/RevisionValueCache.js +18 -6
  37. package/dist/RevisionValueCache.js.map +1 -1
  38. package/dist/RevisionView.js +3 -3
  39. package/dist/RevisionView.js.map +1 -1
  40. package/dist/SerializationUtilities.js +4 -5
  41. package/dist/SerializationUtilities.js.map +1 -1
  42. package/dist/SharedTree.js +69 -33
  43. package/dist/SharedTree.js.map +1 -1
  44. package/dist/SharedTreeEncoder.js +4 -2
  45. package/dist/SharedTreeEncoder.js.map +1 -1
  46. package/dist/StringInterner.js +2 -2
  47. package/dist/StringInterner.js.map +1 -1
  48. package/dist/Summary.js +1 -2
  49. package/dist/Summary.js.map +1 -1
  50. package/dist/SummaryBackCompatibility.js +2 -3
  51. package/dist/SummaryBackCompatibility.js.map +1 -1
  52. package/dist/SummaryTestUtilities.js +2 -3
  53. package/dist/SummaryTestUtilities.js.map +1 -1
  54. package/dist/Transaction.js +4 -0
  55. package/dist/Transaction.js.map +1 -1
  56. package/dist/TransactionInternal.js +8 -7
  57. package/dist/TransactionInternal.js.map +1 -1
  58. package/dist/TreeCompressor.js +2 -0
  59. package/dist/TreeCompressor.js.map +1 -1
  60. package/dist/TreeNodeHandle.js +2 -0
  61. package/dist/TreeNodeHandle.js.map +1 -1
  62. package/dist/TreeView.js +9 -0
  63. package/dist/TreeView.js.map +1 -1
  64. package/dist/TreeViewUtilities.js +3 -4
  65. package/dist/TreeViewUtilities.js.map +1 -1
  66. package/dist/UndoRedoHandler.js +9 -6
  67. package/dist/UndoRedoHandler.js.map +1 -1
  68. package/dist/UuidUtilities.js +6 -6
  69. package/dist/UuidUtilities.js.map +1 -1
  70. package/dist/id-compressor/AppendOnlySortedMap.js +11 -8
  71. package/dist/id-compressor/AppendOnlySortedMap.js.map +1 -1
  72. package/dist/id-compressor/IdCompressor.js +73 -61
  73. package/dist/id-compressor/IdCompressor.js.map +1 -1
  74. package/dist/id-compressor/IdRange.js +1 -2
  75. package/dist/id-compressor/IdRange.js.map +1 -1
  76. package/dist/id-compressor/NumericUuid.js +7 -8
  77. package/dist/id-compressor/NumericUuid.js.map +1 -1
  78. package/dist/id-compressor/SessionIdNormalizer.js +9 -8
  79. package/dist/id-compressor/SessionIdNormalizer.js.map +1 -1
  80. package/dist/migration-shim/migrationDeltaHandler.js +6 -1
  81. package/dist/migration-shim/migrationDeltaHandler.js.map +1 -1
  82. package/dist/migration-shim/migrationShim.js +46 -30
  83. package/dist/migration-shim/migrationShim.js.map +1 -1
  84. package/dist/migration-shim/migrationShimFactory.js +3 -0
  85. package/dist/migration-shim/migrationShimFactory.js.map +1 -1
  86. package/dist/migration-shim/sharedTreeDeltaHandler.js +3 -1
  87. package/dist/migration-shim/sharedTreeDeltaHandler.js.map +1 -1
  88. package/dist/migration-shim/sharedTreeShim.js +7 -0
  89. package/dist/migration-shim/sharedTreeShim.js.map +1 -1
  90. package/dist/migration-shim/sharedTreeShimFactory.js +1 -0
  91. package/dist/migration-shim/sharedTreeShimFactory.js.map +1 -1
  92. package/dist/migration-shim/shimChannelServices.js +1 -0
  93. package/dist/migration-shim/shimChannelServices.js.map +1 -1
  94. package/dist/migration-shim/shimDeltaConnection.js +6 -1
  95. package/dist/migration-shim/shimDeltaConnection.js.map +1 -1
  96. package/dist/migration-shim/shimHandle.js +1 -0
  97. package/dist/migration-shim/shimHandle.js.map +1 -1
  98. package/dist/migration-shim/utils.js +3 -4
  99. package/dist/migration-shim/utils.js.map +1 -1
  100. package/dist/persisted-types/0.1.1.d.ts.map +1 -1
  101. package/lib/ChangeCompression.js +1 -0
  102. package/lib/ChangeCompression.js.map +1 -1
  103. package/lib/ChangeTypes.d.ts.map +1 -1
  104. package/lib/Checkout.js +28 -1
  105. package/lib/Checkout.js.map +1 -1
  106. package/lib/Common.js +1 -1
  107. package/lib/Common.js.map +1 -1
  108. package/lib/EditLog.d.ts.map +1 -1
  109. package/lib/EditLog.js +12 -8
  110. package/lib/EditLog.js.map +1 -1
  111. package/lib/Forest.js +9 -0
  112. package/lib/Forest.js.map +1 -1
  113. package/lib/LazyCheckout.js +1 -0
  114. package/lib/LazyCheckout.js.map +1 -1
  115. package/lib/LogViewer.js +50 -16
  116. package/lib/LogViewer.js.map +1 -1
  117. package/lib/MergeHealth.d.ts.map +1 -1
  118. package/lib/MergeHealth.js +121 -122
  119. package/lib/MergeHealth.js.map +1 -1
  120. package/lib/RevisionValueCache.js +18 -6
  121. package/lib/RevisionValueCache.js.map +1 -1
  122. package/lib/SharedTree.js +69 -33
  123. package/lib/SharedTree.js.map +1 -1
  124. package/lib/SharedTreeEncoder.js +4 -2
  125. package/lib/SharedTreeEncoder.js.map +1 -1
  126. package/lib/StringInterner.js +2 -2
  127. package/lib/StringInterner.js.map +1 -1
  128. package/lib/Transaction.js +4 -0
  129. package/lib/Transaction.js.map +1 -1
  130. package/lib/TransactionInternal.js +8 -7
  131. package/lib/TransactionInternal.js.map +1 -1
  132. package/lib/TreeCompressor.js +2 -0
  133. package/lib/TreeCompressor.js.map +1 -1
  134. package/lib/TreeNodeHandle.js +2 -0
  135. package/lib/TreeNodeHandle.js.map +1 -1
  136. package/lib/TreeView.js +9 -0
  137. package/lib/TreeView.js.map +1 -1
  138. package/lib/UndoRedoHandler.js +9 -6
  139. package/lib/UndoRedoHandler.js.map +1 -1
  140. package/lib/id-compressor/AppendOnlySortedMap.js +11 -8
  141. package/lib/id-compressor/AppendOnlySortedMap.js.map +1 -1
  142. package/lib/id-compressor/IdCompressor.js +69 -57
  143. package/lib/id-compressor/IdCompressor.js.map +1 -1
  144. package/lib/id-compressor/SessionIdNormalizer.js +9 -8
  145. package/lib/id-compressor/SessionIdNormalizer.js.map +1 -1
  146. package/lib/migration-shim/migrationDeltaHandler.js +6 -1
  147. package/lib/migration-shim/migrationDeltaHandler.js.map +1 -1
  148. package/lib/migration-shim/migrationShim.js +46 -30
  149. package/lib/migration-shim/migrationShim.js.map +1 -1
  150. package/lib/migration-shim/migrationShimFactory.js +3 -0
  151. package/lib/migration-shim/migrationShimFactory.js.map +1 -1
  152. package/lib/migration-shim/sharedTreeDeltaHandler.js +3 -1
  153. package/lib/migration-shim/sharedTreeDeltaHandler.js.map +1 -1
  154. package/lib/migration-shim/sharedTreeShim.js +7 -0
  155. package/lib/migration-shim/sharedTreeShim.js.map +1 -1
  156. package/lib/migration-shim/sharedTreeShimFactory.js +1 -0
  157. package/lib/migration-shim/sharedTreeShimFactory.js.map +1 -1
  158. package/lib/migration-shim/shimChannelServices.js +1 -0
  159. package/lib/migration-shim/shimChannelServices.js.map +1 -1
  160. package/lib/migration-shim/shimDeltaConnection.js +6 -1
  161. package/lib/migration-shim/shimDeltaConnection.js.map +1 -1
  162. package/lib/migration-shim/shimHandle.js +1 -0
  163. package/lib/migration-shim/shimHandle.js.map +1 -1
  164. package/lib/persisted-types/0.1.1.d.ts.map +1 -1
  165. package/package.json +37 -41
  166. package/src/MergeHealth.ts +20 -13
  167. package/tsconfig.json +3 -4
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { LogLevel } from '@fluidframework/core-interfaces';
5
6
  import { assertNotUndefined, fail } from './Common.js';
6
7
  import { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';
7
8
  import { SharedTreeEvent } from './EventTypes.js';
@@ -19,7 +20,7 @@ export function useFailedSequencedEditTelemetry(tree) {
19
20
  logger.send({
20
21
  category: 'generic',
21
22
  eventName: outcome.status === EditStatus.Malformed ? 'MalformedSharedTreeEdit' : 'InvalidSharedTreeEdit',
22
- });
23
+ }, LogLevel.essential);
23
24
  }
24
25
  }
25
26
  tree.on(SharedTreeEvent.SequencedEditApplied, onEdit);
@@ -34,127 +35,8 @@ export function useFailedSequencedEditTelemetry(tree) {
34
35
  * @internal
35
36
  */
36
37
  export class SharedTreeMergeHealthTelemetryHeartbeat {
37
- constructor() {
38
- this.heartbeatTimerId = 0;
39
- this.treeData = new Map();
40
- /**
41
- * Receives SequencedEditApplied events from trees.
42
- */
43
- this.sequencedEditHandler = (params) => {
44
- const { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;
45
- if (wasLocal) {
46
- const tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');
47
- tallyAndLogger.logger = logger;
48
- const tally = tallyAndLogger.tally;
49
- tally.editCount += 1;
50
- tally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;
51
- if (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {
52
- tally.maxAttemptCount = edit.pastAttemptCount;
53
- }
54
- if (outcome.status !== EditStatus.Applied) {
55
- tally.failedEditCount += 1;
56
- switch (outcome.failure.kind) {
57
- case TransactionInternal.FailureKind.BadPlace: {
58
- tally.badPlaceCount += 1;
59
- if (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {
60
- tally.deletedSiblingBadPlaceCount += 1;
61
- }
62
- else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {
63
- tally.deletedAncestorBadPlaceCount += 1;
64
- }
65
- break;
66
- }
67
- case TransactionInternal.FailureKind.BadRange: {
68
- tally.badRangeCount += 1;
69
- switch (outcome.failure.rangeFailure) {
70
- case RangeValidationResultKind.Inverted:
71
- tally.updatedRangeInvertedCount += 1;
72
- break;
73
- case RangeValidationResultKind.PlacesInDifferentTraits:
74
- tally.updatedRangeHasPlacesInDifferentTraitsCount += 1;
75
- break;
76
- default:
77
- // 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object
78
- // with a nested 'kind' property containing the RangeValidationResultKind (handled below).
79
- switch (outcome.failure.rangeFailure?.kind) {
80
- case RangeValidationResultKind.BadPlace:
81
- tally.updatedRangeBadPlaceCount += 1;
82
- break;
83
- default:
84
- break;
85
- }
86
- }
87
- break;
88
- }
89
- case TransactionInternal.FailureKind.ConstraintViolation: {
90
- tally.constraintViolationCount += 1;
91
- switch (outcome.failure.violation.kind) {
92
- case TransactionInternal.ConstraintViolationKind.BadRange: {
93
- tally.rangeConstraintViolationCount += 1;
94
- }
95
- case TransactionInternal.ConstraintViolationKind.BadLength: {
96
- tally.lengthConstraintViolationCount += 1;
97
- break;
98
- }
99
- case TransactionInternal.ConstraintViolationKind.BadParent: {
100
- tally.parentConstraintViolationCount += 1;
101
- break;
102
- }
103
- case TransactionInternal.ConstraintViolationKind.BadLabel: {
104
- tally.labelConstraintViolationCount += 1;
105
- break;
106
- }
107
- default: {
108
- // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
109
- const _ = outcome.failure.violation;
110
- }
111
- }
112
- break;
113
- }
114
- case TransactionInternal.FailureKind.IdAlreadyInUse: {
115
- tally.idAlreadyInUseCount += 1;
116
- break;
117
- }
118
- case TransactionInternal.FailureKind.UnknownId: {
119
- tally.unknownIdCount += 1;
120
- break;
121
- }
122
- case TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:
123
- case TransactionInternal.FailureKind.DetachedSequenceNotFound:
124
- case TransactionInternal.FailureKind.DuplicateIdInBuild:
125
- case TransactionInternal.FailureKind.UnusedDetachedSequence: {
126
- tally.malformedEditCount += 1;
127
- break;
128
- }
129
- default: {
130
- // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
131
- const _ = outcome.failure;
132
- }
133
- }
134
- }
135
- }
136
- };
137
- /**
138
- * Logs the accumulated merge health data to each tree's designated logger.
139
- */
140
- this.logHeartbeat = () => {
141
- for (const [tree, { tally, logger }] of this.treeData) {
142
- if (logger && tally.editCount > 0) {
143
- // Note: all this data is for sequenced edits that were originally produced by the local client.
144
- logger.send({
145
- category: 'Heartbeat',
146
- eventName: 'EditMergeHealth',
147
- ...tally,
148
- // The counts of occurrences for a given path length.
149
- // '1:2' means two occurrences of length one.
150
- // Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.
151
- pathLengths: pathLengthsCounts(tally.pathLengths),
152
- });
153
- this.resetTreeData(tree);
154
- }
155
- }
156
- };
157
- }
38
+ heartbeatTimerId = 0;
39
+ treeData = new Map();
158
40
  /**
159
41
  * Adds a tree to the set of tree to log merge health telemetry for.
160
42
  * Noop if such a tree was already in the set.
@@ -259,6 +141,123 @@ export class SharedTreeMergeHealthTelemetryHeartbeat {
259
141
  this.resetTreeData(tree);
260
142
  }
261
143
  }
144
+ /**
145
+ * Receives SequencedEditApplied events from trees.
146
+ */
147
+ sequencedEditHandler = (params) => {
148
+ const { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;
149
+ if (wasLocal) {
150
+ const tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');
151
+ tallyAndLogger.logger = logger;
152
+ const tally = tallyAndLogger.tally;
153
+ tally.editCount += 1;
154
+ tally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;
155
+ if (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {
156
+ tally.maxAttemptCount = edit.pastAttemptCount;
157
+ }
158
+ if (outcome.status !== EditStatus.Applied) {
159
+ tally.failedEditCount += 1;
160
+ switch (outcome.failure.kind) {
161
+ case TransactionInternal.FailureKind.BadPlace: {
162
+ tally.badPlaceCount += 1;
163
+ if (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {
164
+ tally.deletedSiblingBadPlaceCount += 1;
165
+ }
166
+ else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {
167
+ tally.deletedAncestorBadPlaceCount += 1;
168
+ }
169
+ break;
170
+ }
171
+ case TransactionInternal.FailureKind.BadRange: {
172
+ tally.badRangeCount += 1;
173
+ switch (outcome.failure.rangeFailure) {
174
+ case RangeValidationResultKind.Inverted:
175
+ tally.updatedRangeInvertedCount += 1;
176
+ break;
177
+ case RangeValidationResultKind.PlacesInDifferentTraits:
178
+ tally.updatedRangeHasPlacesInDifferentTraitsCount += 1;
179
+ break;
180
+ default:
181
+ // 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object
182
+ // with a nested 'kind' property containing the RangeValidationResultKind (handled below).
183
+ switch (outcome.failure.rangeFailure?.kind) {
184
+ case RangeValidationResultKind.BadPlace:
185
+ tally.updatedRangeBadPlaceCount += 1;
186
+ break;
187
+ default:
188
+ break;
189
+ }
190
+ }
191
+ break;
192
+ }
193
+ case TransactionInternal.FailureKind.ConstraintViolation: {
194
+ tally.constraintViolationCount += 1;
195
+ switch (outcome.failure.violation.kind) {
196
+ case TransactionInternal.ConstraintViolationKind.BadRange: {
197
+ tally.rangeConstraintViolationCount += 1;
198
+ }
199
+ case TransactionInternal.ConstraintViolationKind.BadLength: {
200
+ tally.lengthConstraintViolationCount += 1;
201
+ break;
202
+ }
203
+ case TransactionInternal.ConstraintViolationKind.BadParent: {
204
+ tally.parentConstraintViolationCount += 1;
205
+ break;
206
+ }
207
+ case TransactionInternal.ConstraintViolationKind.BadLabel: {
208
+ tally.labelConstraintViolationCount += 1;
209
+ break;
210
+ }
211
+ default: {
212
+ // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
213
+ const _ = outcome.failure.violation;
214
+ }
215
+ }
216
+ break;
217
+ }
218
+ case TransactionInternal.FailureKind.IdAlreadyInUse: {
219
+ tally.idAlreadyInUseCount += 1;
220
+ break;
221
+ }
222
+ case TransactionInternal.FailureKind.UnknownId: {
223
+ tally.unknownIdCount += 1;
224
+ break;
225
+ }
226
+ case TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:
227
+ case TransactionInternal.FailureKind.DetachedSequenceNotFound:
228
+ case TransactionInternal.FailureKind.DuplicateIdInBuild:
229
+ case TransactionInternal.FailureKind.UnusedDetachedSequence: {
230
+ tally.malformedEditCount += 1;
231
+ break;
232
+ }
233
+ default: {
234
+ // If this doesn't type-check, the above switch statement needs to be extended to handle a new case.
235
+ const _ = outcome.failure;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ };
241
+ /**
242
+ * Logs the accumulated merge health data to each tree's designated logger.
243
+ */
244
+ logHeartbeat = () => {
245
+ for (const [tree, { tally, logger }] of this.treeData) {
246
+ if (logger && tally.editCount > 0) {
247
+ // Note: all this data is for sequenced edits that were originally produced by the local client.
248
+ logger.send({
249
+ category: 'Heartbeat',
250
+ eventName: 'EditMergeHealth',
251
+ ...tally,
252
+ // The counts of occurrences for a given path length.
253
+ // '1:2' means two occurrences of length one.
254
+ // Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.
255
+ pathLengths: pathLengthsCounts(tally.pathLengths),
256
+ }, LogLevel.essential);
257
+ this.resetTreeData(tree);
258
+ }
259
+ }
260
+ };
262
261
  }
263
262
  function pathLengthsCounts(lengths) {
264
263
  return Object.entries(lengths)
@@ -1 +1 @@
1
- {"version":3,"file":"MergeHealth.js","sourceRoot":"","sources":["../src/MergeHealth.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';\n\nimport { assertNotUndefined, fail } from './Common.js';\nimport { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';\nimport { SharedTreeEvent } from './EventTypes.js';\nimport { SequencedEditAppliedEventArguments, SharedTree } from './SharedTree.js';\nimport { TransactionInternal } from './TransactionInternal.js';\nimport { EditStatus } from './persisted-types/index.js';\n\n/**\n * Logs generic telemetry for failed sequenced edits.\n * Only failing edits that were originally made locally are logged.\n * @param tree - The tree for which to log the telemetry.\n * @internal\n */\nexport function useFailedSequencedEditTelemetry(tree: SharedTree): { disable: () => void } {\n\tfunction onEdit({ wasLocal, logger, outcome }: SequencedEditAppliedEventArguments): void {\n\t\tif (wasLocal && outcome.status !== EditStatus.Applied) {\n\t\t\tlogger.send({\n\t\t\t\tcategory: 'generic',\n\t\t\t\teventName: outcome.status === EditStatus.Malformed ? 'MalformedSharedTreeEdit' : 'InvalidSharedTreeEdit',\n\t\t\t});\n\t\t}\n\t}\n\ttree.on(SharedTreeEvent.SequencedEditApplied, onEdit);\n\treturn {\n\t\tdisable: () => {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, onEdit);\n\t\t},\n\t};\n}\n\n/**\n * Statistics about the health of collaborative edit merging when using {@link SharedTree}.\n * All of those numbers constitute a tally since the last heartbeat was logged or cleared.\n * @internal\n */\nexport interface MergeHealthStats {\n\t/** Number of sequenced edits applied (failed or not). */\n\teditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply.\n\t * Such cases are also counted under {@link MergeHealthStats.editCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.badPlaceCount}\n\t * * {@link MergeHealthStats.badRangeCount}\n\t * * {@link MergeHealthStats.constraintViolationCount}\n\t * * {@link MergeHealthStats.idAlreadyInUseCount}\n\t * * {@link MergeHealthStats.unknownIdCount}\n\t * * {@link MergeHealthStats.malformedEditCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tfailedEditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad place.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadPlaceCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad range.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadRangeCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadRangeCount}\n\t * * {@link MergeHealthStats.updatedRangeInvertedCount}\n\t * * {@link MergeHealthStats.updatedRangeHasPlacesInDifferentTraitsCount}\n\t * * {@link MergeHealthStats.updatedRangeBadPlaceCount}\n\t * * {@link MergeHealthStats.updatedRangeNeverValidPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedAncestorBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedAncestorBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose sibling (but not its parent) had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedSiblingBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose delimiting sibling(s) (but not its parent) had been concurrently\n\t * deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedSiblingBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but inverted (i.e., end before start).\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeInvertedCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but in different traits.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeHasPlacesInDifferentTraitsCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but bad.\n\t * This should not happen because place resolution is expected to either return a valid place or fail.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were not resolvable due to not being valid for any state.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeNeverValidPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constraint violation.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.rangeConstraintViolationCount}\n\t * * {@link MergeHealthStats.lengthConstraintViolationCount}\n\t * * {@link MergeHealthStats.parentConstraintViolationCount}\n\t * * {@link MergeHealthStats.labelConstraintViolationCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tconstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range becoming invalid or malformed.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\trangeConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range having a different length.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlengthConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different parent.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tparentConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different label.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlabelConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID collision.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tidAlreadyInUseCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID being unknown.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tunknownIdCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an edit becoming malformed.\n\t * This should theoretically never happen.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tmalformedEditCount: number;\n\n\t/**\n\t * The counts of occurrences for a given path length. `pathLengths[1] === 2` means two occurrences of length one.\n\t */\n\tpathLengths: number[];\n\n\t/** The highest number previous attempts on a sequenced edit. */\n\tmaxAttemptCount: number;\n}\n\n/**\n * Aggregates and logs telemetry about the success of concurrent edits.\n * @internal\n */\nexport class SharedTreeMergeHealthTelemetryHeartbeat {\n\tprivate heartbeatTimerId = 0;\n\tprivate readonly treeData = new Map<SharedTree, { tally: MergeHealthStats; logger?: ITelemetryBaseLogger }>();\n\n\t/**\n\t * Adds a tree to the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was already in the set.\n\t * @param tree - The tree to log merge health telemetry for.\n\t */\n\tpublic attachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree) === false) {\n\t\t\tthis.resetTreeData(tree);\n\t\t\ttree.on(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t}\n\t}\n\n\t/**\n\t * Removes a tree from the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was never in the set.\n\t * @param tree - The tree to stop logging merge health telemetry for.\n\t */\n\tpublic detachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree)) {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t\tthis.treeData.delete(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Exposes the aggregated statistics about merge health for the given tree.\n\t * @param tree - The tree to get stats for.\n\t * @returns Aggregated statistics about merge health for the given tree.\n\t */\n\tpublic getStats(tree: SharedTree): MergeHealthStats {\n\t\treturn assertNotUndefined(this.treeData.get(tree), 'No such tree was attached to the logger').tally;\n\t}\n\n\t/**\n\t * Removes all trees from the set of tree to log merge health telemetry for.\n\t */\n\tpublic detachAllTrees(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.detachTree(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Resets the aggregated merge health data for the given tree.\n\t * @param tree - The tree to reset the merge health data for.\n\t */\n\tpublic resetTreeData(tree: SharedTree): void {\n\t\tthis.treeData.set(tree, {\n\t\t\ttally: {\n\t\t\t\tmaxAttemptCount: 0,\n\t\t\t\tpathLengths: [],\n\t\t\t\teditCount: 0,\n\t\t\t\tfailedEditCount: 0,\n\t\t\t\tbadPlaceCount: 0,\n\t\t\t\tbadRangeCount: 0,\n\n\t\t\t\tdeletedAncestorBadPlaceCount: 0,\n\t\t\t\tdeletedAncestorBadRangeCount: 0,\n\n\t\t\t\tdeletedSiblingBadPlaceCount: 0,\n\t\t\t\tdeletedSiblingBadRangeCount: 0,\n\t\t\t\tupdatedRangeInvertedCount: 0,\n\t\t\t\tupdatedRangeHasPlacesInDifferentTraitsCount: 0,\n\t\t\t\tupdatedRangeBadPlaceCount: 0,\n\t\t\t\tupdatedRangeNeverValidPlaceCount: 0,\n\n\t\t\t\tconstraintViolationCount: 0,\n\t\t\t\trangeConstraintViolationCount: 0,\n\t\t\t\tlengthConstraintViolationCount: 0,\n\t\t\t\tparentConstraintViolationCount: 0,\n\t\t\t\tlabelConstraintViolationCount: 0,\n\n\t\t\t\tidAlreadyInUseCount: 0,\n\t\t\t\tunknownIdCount: 0,\n\t\t\t\tmalformedEditCount: 0,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Enables the regular telemetry logging of merge health data.\n\t * The first message will be sent after `interval` milliseconds. See {@link SharedTreeMergeHealthTelemetryHeartbeat.flushHeartbeat} for\n\t * immediate logging.\n\t * @param interval - The amount of time in milliseconds between log messages.\n\t */\n\tpublic startHeartbeat(interval: number = 60000): void {\n\t\tif (this.heartbeatTimerId !== 0) {\n\t\t\tthis.stopHeartbeat();\n\t\t}\n\t\tthis.heartbeatTimerId = window.setInterval(this.logHeartbeat, interval);\n\t}\n\n\t/**\n\t * Disables the regular telemetry logging of merge health data.\n\t */\n\tpublic stopHeartbeat(): void {\n\t\twindow.clearInterval(this.heartbeatTimerId);\n\t\tthis.heartbeatTimerId = 0;\n\t}\n\n\t/**\n\t * Sends all collected merge health data and resets the aggregated state.\n\t */\n\tpublic flushHeartbeat(): void {\n\t\tthis.logHeartbeat();\n\t\tthis.clearData();\n\t}\n\n\t/**\n\t * Resets all aggregated state.\n\t */\n\tpublic clearData(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.resetTreeData(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Receives SequencedEditApplied events from trees.\n\t */\n\tprivate readonly sequencedEditHandler = (params: SequencedEditAppliedEventArguments): void => {\n\t\tconst { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;\n\t\tif (wasLocal) {\n\t\t\tconst tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');\n\t\t\ttallyAndLogger.logger = logger;\n\t\t\tconst tally = tallyAndLogger.tally;\n\t\t\ttally.editCount += 1;\n\t\t\ttally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;\n\t\t\tif (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {\n\t\t\t\ttally.maxAttemptCount = edit.pastAttemptCount;\n\t\t\t}\n\t\t\tif (outcome.status !== EditStatus.Applied) {\n\t\t\t\ttally.failedEditCount += 1;\n\t\t\t\tswitch (outcome.failure.kind) {\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadPlace: {\n\t\t\t\t\t\ttally.badPlaceCount += 1;\n\t\t\t\t\t\tif (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {\n\t\t\t\t\t\t\ttally.deletedSiblingBadPlaceCount += 1;\n\t\t\t\t\t\t} else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {\n\t\t\t\t\t\t\ttally.deletedAncestorBadPlaceCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadRange: {\n\t\t\t\t\t\ttally.badRangeCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.rangeFailure) {\n\t\t\t\t\t\t\tcase RangeValidationResultKind.Inverted:\n\t\t\t\t\t\t\t\ttally.updatedRangeInvertedCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase RangeValidationResultKind.PlacesInDifferentTraits:\n\t\t\t\t\t\t\t\ttally.updatedRangeHasPlacesInDifferentTraitsCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object\n\t\t\t\t\t\t\t\t// with a nested 'kind' property containing the RangeValidationResultKind (handled below).\n\t\t\t\t\t\t\t\tswitch (outcome.failure.rangeFailure?.kind) {\n\t\t\t\t\t\t\t\t\tcase RangeValidationResultKind.BadPlace:\n\t\t\t\t\t\t\t\t\t\ttally.updatedRangeBadPlaceCount += 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.ConstraintViolation: {\n\t\t\t\t\t\ttally.constraintViolationCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.violation.kind) {\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadRange: {\n\t\t\t\t\t\t\t\ttally.rangeConstraintViolationCount += 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLength: {\n\t\t\t\t\t\t\t\ttally.lengthConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadParent: {\n\t\t\t\t\t\t\t\ttally.parentConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLabel: {\n\t\t\t\t\t\t\t\ttally.labelConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdefault: {\n\t\t\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\t\t\tconst _: never = outcome.failure.violation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.IdAlreadyInUse: {\n\t\t\t\t\t\ttally.idAlreadyInUseCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnknownId: {\n\t\t\t\t\t\ttally.unknownIdCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceNotFound:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DuplicateIdInBuild:\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnusedDetachedSequence: {\n\t\t\t\t\t\ttally.malformedEditCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\tconst _: never = outcome.failure;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Logs the accumulated merge health data to each tree's designated logger.\n\t */\n\tprivate readonly logHeartbeat = (): void => {\n\t\tfor (const [tree, { tally, logger }] of this.treeData) {\n\t\t\tif (logger && tally.editCount > 0) {\n\t\t\t\t// Note: all this data is for sequenced edits that were originally produced by the local client.\n\t\t\t\tlogger.send({\n\t\t\t\t\tcategory: 'Heartbeat',\n\t\t\t\t\teventName: 'EditMergeHealth',\n\t\t\t\t\t...tally,\n\t\t\t\t\t// The counts of occurrences for a given path length.\n\t\t\t\t\t// '1:2' means two occurrences of length one.\n\t\t\t\t\t// Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.\n\t\t\t\t\tpathLengths: pathLengthsCounts(tally.pathLengths),\n\t\t\t\t});\n\t\t\t\tthis.resetTreeData(tree);\n\t\t\t}\n\t\t}\n\t};\n}\n\nfunction pathLengthsCounts(lengths: readonly number[]): string {\n\treturn Object.entries(lengths)\n\t\t.map(([length, count]) => `${length}:${count}`)\n\t\t.join(',');\n}\n"]}
1
+ {"version":3,"file":"MergeHealth.js","sourceRoot":"","sources":["../src/MergeHealth.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { LogLevel } from '@fluidframework/core-interfaces';\nimport type { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';\n\nimport { assertNotUndefined, fail } from './Common.js';\nimport { PlaceValidationResult, RangeValidationResultKind } from './EditUtilities.js';\nimport { SharedTreeEvent } from './EventTypes.js';\nimport { SequencedEditAppliedEventArguments, SharedTree } from './SharedTree.js';\nimport { TransactionInternal } from './TransactionInternal.js';\nimport { EditStatus } from './persisted-types/index.js';\n\n/**\n * Logs generic telemetry for failed sequenced edits.\n * Only failing edits that were originally made locally are logged.\n * @param tree - The tree for which to log the telemetry.\n * @internal\n */\nexport function useFailedSequencedEditTelemetry(tree: SharedTree): { disable: () => void } {\n\tfunction onEdit({ wasLocal, logger, outcome }: SequencedEditAppliedEventArguments): void {\n\t\tif (wasLocal && outcome.status !== EditStatus.Applied) {\n\t\t\tlogger.send(\n\t\t\t\t{\n\t\t\t\t\tcategory: 'generic',\n\t\t\t\t\teventName: outcome.status === EditStatus.Malformed ? 'MalformedSharedTreeEdit' : 'InvalidSharedTreeEdit',\n\t\t\t\t},\n\t\t\t\tLogLevel.essential\n\t\t\t);\n\t\t}\n\t}\n\ttree.on(SharedTreeEvent.SequencedEditApplied, onEdit);\n\treturn {\n\t\tdisable: () => {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, onEdit);\n\t\t},\n\t};\n}\n\n/**\n * Statistics about the health of collaborative edit merging when using {@link SharedTree}.\n * All of those numbers constitute a tally since the last heartbeat was logged or cleared.\n * @internal\n */\nexport interface MergeHealthStats {\n\t/** Number of sequenced edits applied (failed or not). */\n\teditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply.\n\t * Such cases are also counted under {@link MergeHealthStats.editCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.badPlaceCount}\n\t * * {@link MergeHealthStats.badRangeCount}\n\t * * {@link MergeHealthStats.constraintViolationCount}\n\t * * {@link MergeHealthStats.idAlreadyInUseCount}\n\t * * {@link MergeHealthStats.unknownIdCount}\n\t * * {@link MergeHealthStats.malformedEditCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tfailedEditCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad place.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadPlaceCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a bad range.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.deletedAncestorBadRangeCount}\n\t * * {@link MergeHealthStats.deletedSiblingBadRangeCount}\n\t * * {@link MergeHealthStats.updatedRangeInvertedCount}\n\t * * {@link MergeHealthStats.updatedRangeHasPlacesInDifferentTraitsCount}\n\t * * {@link MergeHealthStats.updatedRangeBadPlaceCount}\n\t * * {@link MergeHealthStats.updatedRangeNeverValidPlaceCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tbadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedAncestorBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose ancestors had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedAncestorBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a place whose sibling (but not its parent) had been concurrently deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badPlaceCount}.\n\t */\n\tdeletedSiblingBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose delimiting sibling(s) (but not its parent) had been concurrently\n\t * deleted.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tdeletedSiblingBadRangeCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but inverted (i.e., end before start).\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeInvertedCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but in different traits.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeHasPlacesInDifferentTraitsCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were resolvable but bad.\n\t * This should not happen because place resolution is expected to either return a valid place or fail.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeBadPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a range whose places were not resolvable due to not being valid for any state.\n\t * Such cases are also counted under {@link MergeHealthStats.badRangeCount}.\n\t */\n\tupdatedRangeNeverValidPlaceCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constraint violation.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t *\n\t * If this number is greater than the sum of:\n\t * * {@link MergeHealthStats.rangeConstraintViolationCount}\n\t * * {@link MergeHealthStats.lengthConstraintViolationCount}\n\t * * {@link MergeHealthStats.parentConstraintViolationCount}\n\t * * {@link MergeHealthStats.labelConstraintViolationCount}\n\t *\n\t * then some failure scenarios are not being tracked adequately.\n\t */\n\tconstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range becoming invalid or malformed.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\trangeConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range having a different length.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlengthConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different parent.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tparentConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to a constrained range being under a different label.\n\t * Such cases are also counted under {@link MergeHealthStats.constraintViolationCount}.\n\t */\n\tlabelConstraintViolationCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID collision.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tidAlreadyInUseCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an ID being unknown.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tunknownIdCount: number;\n\n\t/**\n\t * Number of sequenced edits that failed to apply due to an edit becoming malformed.\n\t * This should theoretically never happen.\n\t * Such cases are also counted under {@link MergeHealthStats.failedEditCount}.\n\t */\n\tmalformedEditCount: number;\n\n\t/**\n\t * The counts of occurrences for a given path length. `pathLengths[1] === 2` means two occurrences of length one.\n\t */\n\tpathLengths: number[];\n\n\t/** The highest number previous attempts on a sequenced edit. */\n\tmaxAttemptCount: number;\n}\n\n/**\n * Aggregates and logs telemetry about the success of concurrent edits.\n * @internal\n */\nexport class SharedTreeMergeHealthTelemetryHeartbeat {\n\tprivate heartbeatTimerId = 0;\n\tprivate readonly treeData = new Map<SharedTree, { tally: MergeHealthStats; logger?: ITelemetryBaseLogger }>();\n\n\t/**\n\t * Adds a tree to the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was already in the set.\n\t * @param tree - The tree to log merge health telemetry for.\n\t */\n\tpublic attachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree) === false) {\n\t\t\tthis.resetTreeData(tree);\n\t\t\ttree.on(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t}\n\t}\n\n\t/**\n\t * Removes a tree from the set of tree to log merge health telemetry for.\n\t * Noop if such a tree was never in the set.\n\t * @param tree - The tree to stop logging merge health telemetry for.\n\t */\n\tpublic detachTree(tree: SharedTree): void {\n\t\tif (this.treeData.has(tree)) {\n\t\t\ttree.off(SharedTreeEvent.SequencedEditApplied, this.sequencedEditHandler);\n\t\t\tthis.treeData.delete(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Exposes the aggregated statistics about merge health for the given tree.\n\t * @param tree - The tree to get stats for.\n\t * @returns Aggregated statistics about merge health for the given tree.\n\t */\n\tpublic getStats(tree: SharedTree): MergeHealthStats {\n\t\treturn assertNotUndefined(this.treeData.get(tree), 'No such tree was attached to the logger').tally;\n\t}\n\n\t/**\n\t * Removes all trees from the set of tree to log merge health telemetry for.\n\t */\n\tpublic detachAllTrees(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.detachTree(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Resets the aggregated merge health data for the given tree.\n\t * @param tree - The tree to reset the merge health data for.\n\t */\n\tpublic resetTreeData(tree: SharedTree): void {\n\t\tthis.treeData.set(tree, {\n\t\t\ttally: {\n\t\t\t\tmaxAttemptCount: 0,\n\t\t\t\tpathLengths: [],\n\t\t\t\teditCount: 0,\n\t\t\t\tfailedEditCount: 0,\n\t\t\t\tbadPlaceCount: 0,\n\t\t\t\tbadRangeCount: 0,\n\n\t\t\t\tdeletedAncestorBadPlaceCount: 0,\n\t\t\t\tdeletedAncestorBadRangeCount: 0,\n\n\t\t\t\tdeletedSiblingBadPlaceCount: 0,\n\t\t\t\tdeletedSiblingBadRangeCount: 0,\n\t\t\t\tupdatedRangeInvertedCount: 0,\n\t\t\t\tupdatedRangeHasPlacesInDifferentTraitsCount: 0,\n\t\t\t\tupdatedRangeBadPlaceCount: 0,\n\t\t\t\tupdatedRangeNeverValidPlaceCount: 0,\n\n\t\t\t\tconstraintViolationCount: 0,\n\t\t\t\trangeConstraintViolationCount: 0,\n\t\t\t\tlengthConstraintViolationCount: 0,\n\t\t\t\tparentConstraintViolationCount: 0,\n\t\t\t\tlabelConstraintViolationCount: 0,\n\n\t\t\t\tidAlreadyInUseCount: 0,\n\t\t\t\tunknownIdCount: 0,\n\t\t\t\tmalformedEditCount: 0,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Enables the regular telemetry logging of merge health data.\n\t * The first message will be sent after `interval` milliseconds. See {@link SharedTreeMergeHealthTelemetryHeartbeat.flushHeartbeat} for\n\t * immediate logging.\n\t * @param interval - The amount of time in milliseconds between log messages.\n\t */\n\tpublic startHeartbeat(interval: number = 60000): void {\n\t\tif (this.heartbeatTimerId !== 0) {\n\t\t\tthis.stopHeartbeat();\n\t\t}\n\t\tthis.heartbeatTimerId = window.setInterval(this.logHeartbeat, interval);\n\t}\n\n\t/**\n\t * Disables the regular telemetry logging of merge health data.\n\t */\n\tpublic stopHeartbeat(): void {\n\t\twindow.clearInterval(this.heartbeatTimerId);\n\t\tthis.heartbeatTimerId = 0;\n\t}\n\n\t/**\n\t * Sends all collected merge health data and resets the aggregated state.\n\t */\n\tpublic flushHeartbeat(): void {\n\t\tthis.logHeartbeat();\n\t\tthis.clearData();\n\t}\n\n\t/**\n\t * Resets all aggregated state.\n\t */\n\tpublic clearData(): void {\n\t\tfor (const tree of this.treeData.keys()) {\n\t\t\tthis.resetTreeData(tree);\n\t\t}\n\t}\n\n\t/**\n\t * Receives SequencedEditApplied events from trees.\n\t */\n\tprivate readonly sequencedEditHandler = (params: SequencedEditAppliedEventArguments): void => {\n\t\tconst { edit, tree, wasLocal, logger, outcome, reconciliationPath } = params;\n\t\tif (wasLocal) {\n\t\t\tconst tallyAndLogger = this.treeData.get(tree) ?? fail('Should only receive events for registered trees');\n\t\t\ttallyAndLogger.logger = logger;\n\t\t\tconst tally = tallyAndLogger.tally;\n\t\t\ttally.editCount += 1;\n\t\t\ttally.pathLengths[reconciliationPath.length] = (tally.pathLengths[reconciliationPath.length] ?? 0) + 1;\n\t\t\tif (edit.pastAttemptCount !== undefined && edit.pastAttemptCount > tally.maxAttemptCount) {\n\t\t\t\ttally.maxAttemptCount = edit.pastAttemptCount;\n\t\t\t}\n\t\t\tif (outcome.status !== EditStatus.Applied) {\n\t\t\t\ttally.failedEditCount += 1;\n\t\t\t\tswitch (outcome.failure.kind) {\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadPlace: {\n\t\t\t\t\t\ttally.badPlaceCount += 1;\n\t\t\t\t\t\tif (outcome.failure.placeFailure === PlaceValidationResult.MissingSibling) {\n\t\t\t\t\t\t\ttally.deletedSiblingBadPlaceCount += 1;\n\t\t\t\t\t\t} else if (outcome.failure.placeFailure === PlaceValidationResult.MissingParent) {\n\t\t\t\t\t\t\ttally.deletedAncestorBadPlaceCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.BadRange: {\n\t\t\t\t\t\ttally.badRangeCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.rangeFailure) {\n\t\t\t\t\t\t\tcase RangeValidationResultKind.Inverted:\n\t\t\t\t\t\t\t\ttally.updatedRangeInvertedCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase RangeValidationResultKind.PlacesInDifferentTraits:\n\t\t\t\t\t\t\t\ttally.updatedRangeHasPlacesInDifferentTraitsCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// 'rangeFailure' is either a RangeValidationResultKind (handled above), or an object\n\t\t\t\t\t\t\t\t// with a nested 'kind' property containing the RangeValidationResultKind (handled below).\n\t\t\t\t\t\t\t\tswitch (outcome.failure.rangeFailure?.kind) {\n\t\t\t\t\t\t\t\t\tcase RangeValidationResultKind.BadPlace:\n\t\t\t\t\t\t\t\t\t\ttally.updatedRangeBadPlaceCount += 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.ConstraintViolation: {\n\t\t\t\t\t\ttally.constraintViolationCount += 1;\n\t\t\t\t\t\tswitch (outcome.failure.violation.kind) {\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadRange: {\n\t\t\t\t\t\t\t\ttally.rangeConstraintViolationCount += 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLength: {\n\t\t\t\t\t\t\t\ttally.lengthConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadParent: {\n\t\t\t\t\t\t\t\ttally.parentConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase TransactionInternal.ConstraintViolationKind.BadLabel: {\n\t\t\t\t\t\t\t\ttally.labelConstraintViolationCount += 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdefault: {\n\t\t\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\t\t\tconst _: never = outcome.failure.violation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.IdAlreadyInUse: {\n\t\t\t\t\t\ttally.idAlreadyInUseCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnknownId: {\n\t\t\t\t\t\ttally.unknownIdCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceIdAlreadyInUse:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DetachedSequenceNotFound:\n\t\t\t\t\tcase TransactionInternal.FailureKind.DuplicateIdInBuild:\n\t\t\t\t\tcase TransactionInternal.FailureKind.UnusedDetachedSequence: {\n\t\t\t\t\t\ttally.malformedEditCount += 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\t// If this doesn't type-check, the above switch statement needs to be extended to handle a new case.\n\t\t\t\t\t\tconst _: never = outcome.failure;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Logs the accumulated merge health data to each tree's designated logger.\n\t */\n\tprivate readonly logHeartbeat = (): void => {\n\t\tfor (const [tree, { tally, logger }] of this.treeData) {\n\t\t\tif (logger && tally.editCount > 0) {\n\t\t\t\t// Note: all this data is for sequenced edits that were originally produced by the local client.\n\t\t\t\tlogger.send(\n\t\t\t\t\t{\n\t\t\t\t\t\tcategory: 'Heartbeat',\n\t\t\t\t\t\teventName: 'EditMergeHealth',\n\t\t\t\t\t\t...tally,\n\t\t\t\t\t\t// The counts of occurrences for a given path length.\n\t\t\t\t\t\t// '1:2' means two occurrences of length one.\n\t\t\t\t\t\t// Overwrites `tally.pathLengths` which is incompatible with ITelemetryBaseEvent.\n\t\t\t\t\t\tpathLengths: pathLengthsCounts(tally.pathLengths),\n\t\t\t\t\t},\n\t\t\t\t\tLogLevel.essential\n\t\t\t\t);\n\t\t\t\tthis.resetTreeData(tree);\n\t\t\t}\n\t\t}\n\t};\n}\n\nfunction pathLengthsCounts(lengths: readonly number[]): string {\n\treturn Object.entries(lengths)\n\t\t.map(([length, count]) => `${length}:${count}`)\n\t\t.join(',');\n}\n"]}
@@ -20,6 +20,24 @@ import { compareFiniteNumbers, fail } from './Common.js';
20
20
  * `cacheRetainedValue`
21
21
  */
22
22
  export class RevisionValueCache {
23
+ retentionWindowStart;
24
+ /**
25
+ * A cache of entries for revisions.
26
+ * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).
27
+ * Contains all cached values, regardless of why they are cached (retained, LRU or window).
28
+ */
29
+ sortedEntries = new BTree(undefined, compareFiniteNumbers);
30
+ /**
31
+ * Cache of most recently used evictable entries.
32
+ * Subset of `sortedValues` eligible for eviction:
33
+ * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.
34
+ * Evicts least recently used entries.
35
+ */
36
+ evictableRevisions;
37
+ /**
38
+ * The oldest revision that must be retained in memory.
39
+ */
40
+ retainedRevision;
23
41
  constructor(
24
42
  /**
25
43
  * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).
@@ -35,12 +53,6 @@ export class RevisionValueCache {
35
53
  */
36
54
  retainedRevision) {
37
55
  this.retentionWindowStart = retentionWindowStart;
38
- /**
39
- * A cache of entries for revisions.
40
- * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).
41
- * Contains all cached values, regardless of why they are cached (retained, LRU or window).
42
- */
43
- this.sortedEntries = new BTree(undefined, compareFiniteNumbers);
44
56
  assert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);
45
57
  this.evictableRevisions = new LRU({
46
58
  max: evictableSize,
@@ -1 +1 @@
1
- {"version":3,"file":"RevisionValueCache.js","sourceRoot":"","sources":["../src/RevisionValueCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from '@fluidframework/core-utils/internal';\nimport { BTree } from '@tylerbu/sorted-btree-es6';\nimport LRU from 'lru-cache';\n\nimport { compareFiniteNumbers, fail } from './Common.js';\n\n/**\n * A revision corresponds to an index in an `EditLog`.\n *\n * It is associated with the output `RevisionView` of applying the edit at the index to the previous revision.\n * For example:\n *\n * - revision 0 corresponds to the initialRevision.\n *\n * - revision 1 corresponds to the output of editLog[0] applied to the initialRevision.\n * @alpha\n */\nexport type Revision = number;\n\n/**\n * A cache of `TValue`s corresponding to `Revision`s.\n *\n * A value is kept in cache if it meets any of the following criteria:\n *\n * - The revision is \\>= `retentionWindowStart`\n *\n * - The value has been used recently, meaning getClosestEntry or cacheValue was called with its revision. Note that\n * being returned when a large revision was passed to getClosestEntry does not count.\n *\n * - The value is `retained` meaning it was provided to to constructor in retainedEntries or passed to\n * `cacheRetainedValue`\n */\nexport class RevisionValueCache<TValue> {\n\t/**\n\t * A cache of entries for revisions.\n\t * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).\n\t * Contains all cached values, regardless of why they are cached (retained, LRU or window).\n\t */\n\tprivate readonly sortedEntries = new BTree<Revision, TValue>(undefined, compareFiniteNumbers);\n\n\t/**\n\t * Cache of most recently used evictable entries.\n\t * Subset of `sortedValues` eligible for eviction:\n\t * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.\n\t * Evicts least recently used entries.\n\t */\n\tprivate readonly evictableRevisions: LRU<Revision, TValue>;\n\n\t/**\n\t * The oldest revision that must be retained in memory.\n\t */\n\tprivate retainedRevision?: Revision;\n\n\tpublic constructor(\n\t\t/**\n\t\t * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).\n\t\t */\n\t\tevictableSize: number,\n\t\t/**\n\t\t * The first revision within the retention window. All entries with revisions \\>= retentionWindowStart will be retained.\n\t\t * Must be \\>= 0.\n\t\t */\n\t\tprivate retentionWindowStart: Revision,\n\t\t/**\n\t\t * The oldest revision that must be retained in memory.\n\t\t */\n\t\tretainedRevision?: [Revision, TValue]\n\t) {\n\t\tassert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);\n\t\tthis.evictableRevisions = new LRU({\n\t\t\tmax: evictableSize,\n\t\t\tnoDisposeOnSet: true,\n\t\t\tdispose: (revision) => {\n\t\t\t\tif (revision >= this.retentionWindowStart) {\n\t\t\t\t\tfail('Entries in retention window should never be evicted.');\n\t\t\t\t}\n\t\t\t\tif (this.retainedRevision === revision) {\n\t\t\t\t\tfail('Retained entries should not be evicted');\n\t\t\t\t}\n\t\t\t\tthis.sortedEntries.delete(revision);\n\t\t\t},\n\t\t});\n\n\t\tif (retainedRevision !== undefined) {\n\t\t\tthis.cacheRetainedValue(retainedRevision[0], retainedRevision[1]);\n\t\t}\n\t}\n\n\t/**\n\t * @returns if the supplied revision is within the retention window.\n\t */\n\tpublic isWithinRetentionWindow(revision: Revision): boolean {\n\t\treturn revision >= this.retentionWindowStart;\n\t}\n\n\t/**\n\t * Sets the new retention window.\n\t * @param newRetentionWindowStart - defines the trailing edge (inclusive) of the new retention window.\n\t */\n\tpublic updateRetentionWindow(newRetentionWindowStart: Revision): void {\n\t\tif (newRetentionWindowStart < this.retentionWindowStart) {\n\t\t\tfail('retention window boundary must not move backwards');\n\t\t}\n\t\tconst prevRetentionWindowStart = this.retentionWindowStart;\n\t\tthis.retentionWindowStart = newRetentionWindowStart;\n\t\tconst oldWindowEntries: [Revision, TValue][] = [];\n\t\tthis.sortedEntries.forRange(\n\t\t\tprevRetentionWindowStart,\n\t\t\tthis.retentionWindowStart,\n\t\t\tfalse,\n\t\t\t(windowRevision, windowEntry) => {\n\t\t\t\tif (this.retainedRevision !== windowRevision) {\n\t\t\t\t\t// Adding to the LRU can cause eviction which in turn mutates the b-tree we are enumerating. Thus, store list of\n\t\t\t\t\t// old window entries separately.\n\t\t\t\t\toldWindowEntries.push([windowRevision, windowEntry]);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\toldWindowEntries.forEach(([revision, value]) => {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t});\n\t}\n\n\t/**\n\t * @returns a [cachedRevision, value] where cachedRevision \\<= requestedRevision, or undefined if no such revision\n\t * is cached.\n\t */\n\tpublic getClosestEntry(requestedRevision: Revision): [revision: Revision, value: TValue] | undefined {\n\t\tconst fromLRU = this.evictableRevisions.get(requestedRevision);\n\t\tif (fromLRU !== undefined) {\n\t\t\treturn [requestedRevision, fromLRU];\n\t\t}\n\t\treturn this.sortedEntries.getPairOrNextLower(requestedRevision) ?? undefined;\n\t}\n\n\t/**\n\t * Caches the supplied value and guarantees it will never be evicted.\n\t * This will make the previously retained value evictable.\n\t */\n\tpublic cacheRetainedValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision !== undefined) {\n\t\t\tthis.sortedEntries.delete(this.retainedRevision);\n\t\t}\n\t\tthis.retainedRevision = revision;\n\t\tthis.sortedEntries.set(revision, value);\n\t}\n\n\t/**\n\t * Caches the supplied value.\n\t * The cached value is subject to eviction unless it is within the retention window or was previously added\n\t * via `cacheRetainedValue`.\n\t * Note that if a non-retained entry starts out within the retention window and passes outside of it due to a call to\n\t * updateRetentionWindow it is then subject to eviction.\n\t */\n\tpublic cacheValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision === revision) {\n\t\t\treturn;\n\t\t}\n\t\tthis.sortedEntries.set(revision, value);\n\t\tif (revision < this.retentionWindowStart) {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"RevisionValueCache.js","sourceRoot":"","sources":["../src/RevisionValueCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from '@fluidframework/core-utils/internal';\nimport { BTree } from '@tylerbu/sorted-btree-es6';\nimport LRU from 'lru-cache';\n\nimport { compareFiniteNumbers, fail } from './Common.js';\n\n/**\n * A revision corresponds to an index in an `EditLog`.\n *\n * It is associated with the output `RevisionView` of applying the edit at the index to the previous revision.\n * For example:\n *\n * - revision 0 corresponds to the initialRevision.\n *\n * - revision 1 corresponds to the output of editLog[0] applied to the initialRevision.\n * @alpha\n */\nexport type Revision = number;\n\n/**\n * A cache of `TValue`s corresponding to `Revision`s.\n *\n * A value is kept in cache if it meets any of the following criteria:\n *\n * - The revision is \\>= `retentionWindowStart`\n *\n * - The value has been used recently, meaning getClosestEntry or cacheValue was called with its revision. Note that\n * being returned when a large revision was passed to getClosestEntry does not count.\n *\n * - The value is `retained` meaning it was provided to to constructor in retainedEntries or passed to\n * `cacheRetainedValue`\n */\nexport class RevisionValueCache<TValue> {\n\t/**\n\t * A cache of entries for revisions.\n\t * This is sorted to allow efficient access to the nearest preceding entry (see getClosestEntry).\n\t * Contains all cached values, regardless of why they are cached (retained, LRU or window).\n\t */\n\tprivate readonly sortedEntries = new BTree<Revision, TValue>(undefined, compareFiniteNumbers);\n\n\t/**\n\t * Cache of most recently used evictable entries.\n\t * Subset of `sortedValues` eligible for eviction:\n\t * All entries are also in `sortedValues`, and are removed from `sortedValues` when evicted from this cache.\n\t * Evicts least recently used entries.\n\t */\n\tprivate readonly evictableRevisions: LRU<Revision, TValue>;\n\n\t/**\n\t * The oldest revision that must be retained in memory.\n\t */\n\tprivate retainedRevision?: Revision;\n\n\tpublic constructor(\n\t\t/**\n\t\t * Maximum capacity for evictable cache entries (those neither marked as retained nor within the retention window).\n\t\t */\n\t\tevictableSize: number,\n\t\t/**\n\t\t * The first revision within the retention window. All entries with revisions \\>= retentionWindowStart will be retained.\n\t\t * Must be \\>= 0.\n\t\t */\n\t\tprivate retentionWindowStart: Revision,\n\t\t/**\n\t\t * The oldest revision that must be retained in memory.\n\t\t */\n\t\tretainedRevision?: [Revision, TValue]\n\t) {\n\t\tassert(retentionWindowStart >= 0, 0x62c /* retentionWindowStart must be initialized >= 0 */);\n\t\tthis.evictableRevisions = new LRU({\n\t\t\tmax: evictableSize,\n\t\t\tnoDisposeOnSet: true,\n\t\t\tdispose: (revision) => {\n\t\t\t\tif (revision >= this.retentionWindowStart) {\n\t\t\t\t\tfail('Entries in retention window should never be evicted.');\n\t\t\t\t}\n\t\t\t\tif (this.retainedRevision === revision) {\n\t\t\t\t\tfail('Retained entries should not be evicted');\n\t\t\t\t}\n\t\t\t\tthis.sortedEntries.delete(revision);\n\t\t\t},\n\t\t});\n\n\t\tif (retainedRevision !== undefined) {\n\t\t\tthis.cacheRetainedValue(retainedRevision[0], retainedRevision[1]);\n\t\t}\n\t}\n\n\t/**\n\t * @returns if the supplied revision is within the retention window.\n\t */\n\tpublic isWithinRetentionWindow(revision: Revision): boolean {\n\t\treturn revision >= this.retentionWindowStart;\n\t}\n\n\t/**\n\t * Sets the new retention window.\n\t * @param newRetentionWindowStart - defines the trailing edge (inclusive) of the new retention window.\n\t */\n\tpublic updateRetentionWindow(newRetentionWindowStart: Revision): void {\n\t\tif (newRetentionWindowStart < this.retentionWindowStart) {\n\t\t\tfail('retention window boundary must not move backwards');\n\t\t}\n\t\tconst prevRetentionWindowStart = this.retentionWindowStart;\n\t\tthis.retentionWindowStart = newRetentionWindowStart;\n\t\tconst oldWindowEntries: [Revision, TValue][] = [];\n\t\tthis.sortedEntries.forRange(\n\t\t\tprevRetentionWindowStart,\n\t\t\tthis.retentionWindowStart,\n\t\t\tfalse,\n\t\t\t(windowRevision, windowEntry) => {\n\t\t\t\tif (this.retainedRevision !== windowRevision) {\n\t\t\t\t\t// Adding to the LRU can cause eviction which in turn mutates the b-tree we are enumerating. Thus, store list of\n\t\t\t\t\t// old window entries separately.\n\t\t\t\t\toldWindowEntries.push([windowRevision, windowEntry]);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\toldWindowEntries.forEach(([revision, value]) => {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t});\n\t}\n\n\t/**\n\t * @returns a [cachedRevision, value] where cachedRevision \\<= requestedRevision, or undefined if no such revision\n\t * is cached.\n\t */\n\tpublic getClosestEntry(requestedRevision: Revision): [revision: Revision, value: TValue] | undefined {\n\t\tconst fromLRU = this.evictableRevisions.get(requestedRevision);\n\t\tif (fromLRU !== undefined) {\n\t\t\treturn [requestedRevision, fromLRU];\n\t\t}\n\t\treturn this.sortedEntries.getPairOrNextLower(requestedRevision) ?? undefined;\n\t}\n\n\t/**\n\t * Caches the supplied value and guarantees it will never be evicted.\n\t * This will make the previously retained value evictable.\n\t */\n\tpublic cacheRetainedValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision !== undefined) {\n\t\t\tthis.sortedEntries.delete(this.retainedRevision);\n\t\t}\n\t\tthis.retainedRevision = revision;\n\t\tthis.sortedEntries.set(revision, value);\n\t}\n\n\t/**\n\t * Caches the supplied value.\n\t * The cached value is subject to eviction unless it is within the retention window or was previously added\n\t * via `cacheRetainedValue`.\n\t * Note that if a non-retained entry starts out within the retention window and passes outside of it due to a call to\n\t * updateRetentionWindow it is then subject to eviction.\n\t */\n\tpublic cacheValue(revision: Revision, value: TValue): void {\n\t\tif (this.retainedRevision === revision) {\n\t\t\treturn;\n\t\t}\n\t\tthis.sortedEntries.set(revision, value);\n\t\tif (revision < this.retentionWindowStart) {\n\t\t\tthis.evictableRevisions.set(revision, value);\n\t\t}\n\t}\n}\n"]}
package/lib/SharedTree.js CHANGED
@@ -33,6 +33,15 @@ import { SharedTreeAttributes, SharedTreeFactoryType } from './publicContracts.j
33
33
  * @alpha
34
34
  */
35
35
  export class SharedTreeFactory {
36
+ /**
37
+ * {@inheritDoc @fluidframework/shared-object-base#ISharedObjectFactory."type"}
38
+ */
39
+ static Type = SharedTreeFactoryType;
40
+ /**
41
+ * {@inheritDoc @fluidframework/shared-object-base#ISharedObjectFactory.attributes}
42
+ */
43
+ static Attributes = SharedTreeAttributes;
44
+ args;
36
45
  /**
37
46
  * Get a factory for SharedTree to register with the data store.
38
47
  * @param writeFormat - Determines the format version the SharedTree will write ops and summaries in. See {@link https://github.com/microsoft/FluidFramework/blob/main/experimental/dds/tree/docs/Write-Format.md | the write format
@@ -85,14 +94,6 @@ export class SharedTreeFactory {
85
94
  }
86
95
  }
87
96
  }
88
- /**
89
- * {@inheritDoc @fluidframework/shared-object-base#ISharedObjectFactory."type"}
90
- */
91
- SharedTreeFactory.Type = SharedTreeFactoryType;
92
- /**
93
- * {@inheritDoc @fluidframework/shared-object-base#ISharedObjectFactory.attributes}
94
- */
95
- SharedTreeFactory.Attributes = SharedTreeAttributes;
96
97
  /**
97
98
  * Filename where the snapshot is stored.
98
99
  */
@@ -111,6 +112,7 @@ const stashedSessionId = '8477b8d5-cf6c-4673-8345-8f076a8f9bc6';
111
112
  * @alpha
112
113
  */
113
114
  export class SharedTree extends SharedObject {
115
+ writeFormat;
114
116
  /**
115
117
  * Create a new SharedTree. It will contain the default value (see initialTree).
116
118
  */
@@ -144,12 +146,71 @@ export class SharedTree extends SharedObject {
144
146
  }
145
147
  }
146
148
  }
149
+ /**
150
+ * This is SharedTree's internal IdCompressor that predates the one in the runtime. If access
151
+ * to the IdCompressor is needed, this is the one that should be used.
152
+ */
153
+ idCompressor;
154
+ idNormalizer = {
155
+ tree: this,
156
+ get localSessionId() {
157
+ return this.tree.idCompressor.localSessionId;
158
+ },
159
+ normalizeToOpSpace: (id) => this.idCompressor.normalizeToOpSpace(id),
160
+ normalizeToSessionSpace: (id, sessionId) => this.idCompressor.normalizeToSessionSpace(id, sessionId),
161
+ };
162
+ /** Temporarily created to apply stashed ops from a previous session */
163
+ stashedIdCompressor;
164
+ // The initial tree's definition isn't included in any op by default but it should still be interned. Including it here ensures that.
165
+ interner = new MutableStringInterner([initialTree.definition]);
166
+ /**
167
+ * The log of completed edits for this SharedTree.
168
+ */
169
+ editLog;
170
+ editLogSize;
171
+ editEvictionFrequency;
172
+ /**
173
+ * As an implementation detail, SharedTree uses a log viewer that caches views of different revisions.
174
+ * It is not exposed to avoid accidental correctness issues, but `logViewer` is exposed in order to give clients a way
175
+ * to access the revision history.
176
+ */
177
+ cachingLogViewer;
147
178
  /**
148
179
  * Viewer for trees defined by editLog. This allows access to views of the tree at different revisions (various points in time).
149
180
  */
150
181
  get logViewer() {
151
182
  return this.cachingLogViewer;
152
183
  }
184
+ /**
185
+ * logger for SharedTree events.
186
+ */
187
+ logger;
188
+ /**
189
+ * Internal and only viable logger for SharedTree events.
190
+ * @privateRemarks
191
+ * `logger` is public and remains typed for external use with mostly deprecated methods.
192
+ * Internally, only loggerInternal should be used and it provides the full set of logging methods.
193
+ */
194
+ loggerInternal;
195
+ sequencedEditAppliedLogger;
196
+ encoder_0_0_2;
197
+ encoder_0_1_1;
198
+ processEditResult = (editResult, editId) => {
199
+ // TODO:#44859: Invalid results should be handled by the app
200
+ this.emit(SharedTree.eventFromEditResult(editResult), editId);
201
+ };
202
+ processSequencedEditResult = ({ edit, wasLocal, result, reconciliationPath, }) => {
203
+ const eventArguments = {
204
+ edit,
205
+ wasLocal,
206
+ tree: this,
207
+ logger: this.sequencedEditAppliedLogger,
208
+ reconciliationPath,
209
+ outcome: result,
210
+ };
211
+ this.emit(SharedTreeEvent.SequencedEditApplied, eventArguments);
212
+ };
213
+ summarizeHistory;
153
214
  getHistoryPolicy(options) {
154
215
  const noCompatOptions = options;
155
216
  return typeof noCompatOptions.summarizeHistory === 'object'
@@ -163,31 +224,6 @@ export class SharedTree extends SharedObject {
163
224
  constructor(runtime, id, writeFormat, options = {}) {
164
225
  super(id, runtime, SharedTreeFactory.Attributes, 'fluid_legacySharedTree_');
165
226
  this.writeFormat = writeFormat;
166
- this.idNormalizer = {
167
- tree: this,
168
- get localSessionId() {
169
- return this.tree.idCompressor.localSessionId;
170
- },
171
- normalizeToOpSpace: (id) => this.idCompressor.normalizeToOpSpace(id),
172
- normalizeToSessionSpace: (id, sessionId) => this.idCompressor.normalizeToSessionSpace(id, sessionId),
173
- };
174
- // The initial tree's definition isn't included in any op by default but it should still be interned. Including it here ensures that.
175
- this.interner = new MutableStringInterner([initialTree.definition]);
176
- this.processEditResult = (editResult, editId) => {
177
- // TODO:#44859: Invalid results should be handled by the app
178
- this.emit(SharedTree.eventFromEditResult(editResult), editId);
179
- };
180
- this.processSequencedEditResult = ({ edit, wasLocal, result, reconciliationPath, }) => {
181
- const eventArguments = {
182
- edit,
183
- wasLocal,
184
- tree: this,
185
- logger: this.sequencedEditAppliedLogger,
186
- reconciliationPath,
187
- outcome: result,
188
- };
189
- this.emit(SharedTreeEvent.SequencedEditApplied, eventArguments);
190
- };
191
227
  const historyPolicy = this.getHistoryPolicy(options);
192
228
  this.summarizeHistory = historyPolicy.summarizeHistory;
193
229
  this.loggerInternal = createChildLogger({