From 0c6fff8ab69f12ed2e128fcf1bbf32b8c3a365e1 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Sat, 14 May 2022 12:49:31 -0700 Subject: [PATCH 01/14] Initial change to support xfluidtelemetry and content-encoding --- .../odsp-doclib-utils/src/odspErrorUtils.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts index a36abb9cab96..0d84cdcd7fa7 100644 --- a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts +++ b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts @@ -30,7 +30,28 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string interface LoggingHeader { headerName: string; logName: string; + subKeyName?: string; + mapping?: Map; } + const mapResponseOrigin: Map = new Map([ + ["c", "cache"], + ["g", "graph"], + ]); + + const xfluidTelemetry = "x-fluid-telemetry"; + // X-Fluid-Telemetry contains a key value pair in the following format: + // X-Fluid-Telemetry:key1=value1,key2,key3=value3, + // Ex. X-Fluid-Telemetry:Origin=c,isSomeDataPoint (there will be no isSomeDataPoint=false or isSomeDataPoint=True) + const xFluidTelemetryParser = (fluidTelemetry: string, fieldName: string) => { + const outputMap = fluidTelemetry.split(",").map((keyValuePair) => keyValuePair.split("=")); + for (const [key, value] of outputMap) { + if (key.trim() === fieldName) { + return value?.trim() ?? true; + } + } + return undefined; + }; + // We rename headers so that otel doesn't scrub them away. Otel doesn't allow // certain characters in headers including '-' const headersToLog: LoggingHeader[] = [ @@ -39,6 +60,9 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string { headerName: "client-request-id", logName: "clientRequestId" }, { headerName: "x-msedge-ref", logName: "xMsedgeRef" }, { headerName: "X-Fluid-Retries", logName: "serverRetries" }, + { headerName: "content-encoding", logName: "contentEncoding" }, + { headerName: "content-type", logName: "contentType" }, + { headerName: xfluidTelemetry, logName: "responseOrigin", subKeyName: "Origin", mapping: mapResponseOrigin }, ]; const additionalProps: ITelemetryProperties = { sprequestduration: TelemetryLogger.numberFromString(headers.get("sprequestduration")), @@ -47,7 +71,14 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string headersToLog.forEach((header) => { const headerValue = headers.get(header.headerName); if (headerValue !== undefined && headerValue !== null) { - additionalProps[header.logName] = headerValue; + if (header.subKeyName === undefined) { + additionalProps[header.logName] = headerValue; + } else if (header.headerName === xfluidTelemetry) { + const fieldValue = xFluidTelemetryParser(headerValue, header.subKeyName); + if (fieldValue !== undefined) { + additionalProps[header.logName] = header.mapping?.get(fieldValue) ?? fieldValue; + } + } } }); return additionalProps; From dc7ac60c57d0ea9e76078ab8782b67c2f037b421 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Sun, 15 May 2022 13:27:51 -0700 Subject: [PATCH 02/14] Separating the logic for when we have additional fields to process. --- .../odsp-doclib-utils/src/odspErrorUtils.ts | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts index 0d84cdcd7fa7..072529c7fa90 100644 --- a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts +++ b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts @@ -30,27 +30,8 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string interface LoggingHeader { headerName: string; logName: string; - subKeyName?: string; mapping?: Map; } - const mapResponseOrigin: Map = new Map([ - ["c", "cache"], - ["g", "graph"], - ]); - - const xfluidTelemetry = "x-fluid-telemetry"; - // X-Fluid-Telemetry contains a key value pair in the following format: - // X-Fluid-Telemetry:key1=value1,key2,key3=value3, - // Ex. X-Fluid-Telemetry:Origin=c,isSomeDataPoint (there will be no isSomeDataPoint=false or isSomeDataPoint=True) - const xFluidTelemetryParser = (fluidTelemetry: string, fieldName: string) => { - const outputMap = fluidTelemetry.split(",").map((keyValuePair) => keyValuePair.split("=")); - for (const [key, value] of outputMap) { - if (key.trim() === fieldName) { - return value?.trim() ?? true; - } - } - return undefined; - }; // We rename headers so that otel doesn't scrub them away. Otel doesn't allow // certain characters in headers including '-' @@ -62,7 +43,6 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string { headerName: "X-Fluid-Retries", logName: "serverRetries" }, { headerName: "content-encoding", logName: "contentEncoding" }, { headerName: "content-type", logName: "contentType" }, - { headerName: xfluidTelemetry, logName: "responseOrigin", subKeyName: "Origin", mapping: mapResponseOrigin }, ]; const additionalProps: ITelemetryProperties = { sprequestduration: TelemetryLogger.numberFromString(headers.get("sprequestduration")), @@ -71,16 +51,34 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string headersToLog.forEach((header) => { const headerValue = headers.get(header.headerName); if (headerValue !== undefined && headerValue !== null) { - if (header.subKeyName === undefined) { - additionalProps[header.logName] = headerValue; - } else if (header.headerName === xfluidTelemetry) { - const fieldValue = xFluidTelemetryParser(headerValue, header.subKeyName); - if (fieldValue !== undefined) { - additionalProps[header.logName] = header.mapping?.get(fieldValue) ?? fieldValue; - } - } + additionalProps[header.logName] = headerValue; } }); + + // x-fluid-telemetry contains a key value pair in the following format: + // x-fluid-telemetry:key1=value1,key2,key3=value3, + // Ex. x-fluid-telemetry:Origin=c,isSomeDataPoint (there will be no isSomeDataPoint=false or isSomeDataPoint=True) + const fluidTelemetry = headers.get("x-fluid-telemetry"); + if (fluidTelemetry !== undefined && fluidTelemetry !== null) { + const mapResponseOrigin: Map = new Map([ + ["c", "cache"], + ["g", "graph"], + ]); + + const fluidKeyValuesToLog: LoggingHeader[] = [ + { headerName: "Origin", logName: "responseOrigin", mapping: mapResponseOrigin }, + ]; + + const keyValueMap = fluidTelemetry.split(",").map((keyValuePair) => keyValuePair.split("=")); + for (const [key, value] of keyValueMap) { + const fluidKV = fluidKeyValuesToLog.find((t) => t.headerName === key.trim()); + if (fluidKV !== undefined) { + const fieldValue = value?.trim() ?? true; + additionalProps[fluidKV.logName] = fluidKV.mapping?.get(fieldValue) ?? fieldValue; + } + } + } + return additionalProps; } From e00da81b004338de373bc7c35f88f8bbd512954b Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Mon, 16 May 2022 11:33:10 -0700 Subject: [PATCH 03/14] Adding comment for key without value --- packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts index 072529c7fa90..ed618a7bedd6 100644 --- a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts +++ b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts @@ -73,7 +73,7 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string for (const [key, value] of keyValueMap) { const fluidKV = fluidKeyValuesToLog.find((t) => t.headerName === key.trim()); if (fluidKV !== undefined) { - const fieldValue = value?.trim() ?? true; + const fieldValue = value?.trim() ?? true; // assume true for a key without value. additionalProps[fluidKV.logName] = fluidKV.mapping?.get(fieldValue) ?? fieldValue; } } From d3f438c9914abd7170c1790011ed3031d8f3e9b8 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 18 May 2022 16:21:05 -0700 Subject: [PATCH 04/14] Addressing vlad's comments. --- .../odsp-doclib-utils/src/odspErrorUtils.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts index ed618a7bedd6..d128af659c75 100644 --- a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts +++ b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts @@ -30,7 +30,6 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string interface LoggingHeader { headerName: string; logName: string; - mapping?: Map; } // We rename headers so that otel doesn't scrub them away. Otel doesn't allow @@ -60,25 +59,26 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string // Ex. x-fluid-telemetry:Origin=c,isSomeDataPoint (there will be no isSomeDataPoint=false or isSomeDataPoint=True) const fluidTelemetry = headers.get("x-fluid-telemetry"); if (fluidTelemetry !== undefined && fluidTelemetry !== null) { - const mapResponseOrigin: Map = new Map([ - ["c", "cache"], - ["g", "graph"], - ]); - - const fluidKeyValuesToLog: LoggingHeader[] = [ - { headerName: "Origin", logName: "responseOrigin", mapping: mapResponseOrigin }, - ]; - const keyValueMap = fluidTelemetry.split(",").map((keyValuePair) => keyValuePair.split("=")); for (const [key, value] of keyValueMap) { - const fluidKV = fluidKeyValuesToLog.find((t) => t.headerName === key.trim()); - if (fluidKV !== undefined) { - const fieldValue = value?.trim() ?? true; // assume true for a key without value. - additionalProps[fluidKV.logName] = fluidKV.mapping?.get(fieldValue) ?? fieldValue; - } - } + if ("Origin" === key.trim()) { + let fieldValue: string; + switch (value?.trim()) { + case "c": + fieldValue = "cache"; + break; + case "g": + fieldValue = "graph"; + break; + default: + fieldValue = "undefined"; + } + const logName = "responseOrigin"; + additionalProps[logName] = fieldValue; + break; + } + } } - return additionalProps; } From 36f6b714c62033b3b9a290306f0f82713e0e7404 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 18 May 2022 17:16:42 -0700 Subject: [PATCH 05/14] changing name to better identify the fieldValue we get and are not expecting --- packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts index d128af659c75..afe5c172a3d0 100644 --- a/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts +++ b/packages/utils/odsp-doclib-utils/src/odspErrorUtils.ts @@ -56,7 +56,7 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string // x-fluid-telemetry contains a key value pair in the following format: // x-fluid-telemetry:key1=value1,key2,key3=value3, - // Ex. x-fluid-telemetry:Origin=c,isSomeDataPoint (there will be no isSomeDataPoint=false or isSomeDataPoint=True) + // Ex. x-fluid-telemetry:Origin=c const fluidTelemetry = headers.get("x-fluid-telemetry"); if (fluidTelemetry !== undefined && fluidTelemetry !== null) { const keyValueMap = fluidTelemetry.split(",").map((keyValuePair) => keyValuePair.split("=")); @@ -71,7 +71,7 @@ export function getSPOAndGraphRequestIdsFromResponse(headers: { get: (id: string fieldValue = "graph"; break; default: - fieldValue = "undefined"; + fieldValue = value?.trim(); } const logName = "responseOrigin"; additionalProps[logName] = fieldValue; From 4443a59a2f0d53e3d3cb1f1b7a1589f16e023dd5 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 25 May 2022 14:16:06 -0700 Subject: [PATCH 06/14] Draft Summary docs --- .../lib/protocol-definitions/src/summary.ts | 51 ++++++++++++++++--- .../datastore-definitions/src/channel.ts | 6 +++ .../runtime-definitions/src/summary.ts | 14 +++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index ba50e306f321..a30cc5783a98 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -21,41 +21,80 @@ export interface ISummaryCommitter { date: string; } +/** + * Represents a leaf node from the Summary Tree. + */ +// eslint-disable-next-line @typescript-eslint/no-namespace export namespace SummaryType { export type Tree = 1; export type Blob = 2; export type Handle = 3; export type Attachment = 4; - export const Tree: Tree = 1 as const; - export const Blob: Blob = 2 as const; - export const Handle: Handle = 3 as const; - export const Attachment: Attachment = 4 as const; + /** + * Another recursive data structure. + */ + export const Tree: Tree = 1 as const; + + /** + * Binary data to be uploaded to the server. + */ + export const Blob: Blob = 2 as const; + /** + * Path to an already stored tree that hasn't changed since the last summary. + */ + export const Handle: Handle = 3 as const; + + /** + * Handle to blobs uploaded outside of the summary. + */ + export const Attachment: Attachment = 4 as const; } export type SummaryType = SummaryType.Attachment | SummaryType.Blob | SummaryType.Handle | SummaryType.Tree; export type SummaryTypeNoHandle = SummaryType.Tree | SummaryType.Blob | SummaryType.Attachment; +/** + * Path to a previous summary, that hasn't changed since then. + * To illustrate, if a DataStore did not get any ops since last summary, the framework runtime will use a handle for the + * entire DataStore instead of re-sending the entire subtree. Same concept will be applied for a DDS. + * Notice that handles are optimizations from the Fluid Framework Runtime. + */ export interface ISummaryHandle { type: SummaryType.Handle; - // No handles, all other SummaryType are Ok + /** + * All Summary types are supported, with the exception of the handles which is NOT supported here. + */ handleType: SummaryTypeNoHandle; - // Stored handle reference + /** + * Unique path that identifies the stored handle reference. + */ handle: string; } +/** + * Binary data to be uploaded to the server as part of the document's Summary. + */ export interface ISummaryBlob { type: SummaryType.Blob; content: string | Uint8Array; } +/** + * Handle to blobs uploaded outside of the summary. Attachment Blobs are uploaded and downloaded separately via + * http requests and are not included on the snapshot payload. The ISummaryAttachment are handles to these blobs. + * Additional information can be found here: https://github.com/microsoft/FluidFramework/issues/6374 + */ export interface ISummaryAttachment { type: SummaryType.Attachment; id: string; } +/** + * Recursive data structure that is composed by leaf nodes of SummaryObject type. + */ export interface ISummaryTree { type: SummaryType.Tree; diff --git a/packages/runtime/datastore-definitions/src/channel.ts b/packages/runtime/datastore-definitions/src/channel.ts index b7fef85193a8..fbf33449f0c4 100644 --- a/packages/runtime/datastore-definitions/src/channel.ts +++ b/packages/runtime/datastore-definitions/src/channel.ts @@ -21,6 +21,9 @@ export interface IChannel extends IFluidLoadable { /** * Generates summary of the channel synchronously. + * @param fullTree - flag indicating whether the attempt should generate a full + * summary tree without any handles for unchanged subtrees. + * @param trackState - This tells whether we should track state from this summary. * @returns A tree representing the summary of the channel. */ getAttachSummary(fullTree?: boolean, trackState?: boolean): ISummaryTreeWithStats; @@ -28,6 +31,9 @@ export interface IChannel extends IFluidLoadable { /** * Generates summary of the channel asynchronously. * This should not be called where the channel can be modified while summarization is in progress. + * @param fullTree - flag indicating whether the attempt should generate a full + * summary tree without any handles for unchanged subtrees. + * @param trackState - This tells whether we should track state from this summary. * @returns A tree representing the summary of the channel. */ summarize(fullTree?: boolean, trackState?: boolean): Promise; diff --git a/packages/runtime/runtime-definitions/src/summary.ts b/packages/runtime/runtime-definitions/src/summary.ts index f8e1ba70aca7..39eaced286e2 100644 --- a/packages/runtime/runtime-definitions/src/summary.ts +++ b/packages/runtime/runtime-definitions/src/summary.ts @@ -16,6 +16,9 @@ import { IGarbageCollectionSummaryDetails, } from "./garbageCollection"; +/** + * Contains the aggregation data from a Tree/Subtree. + */ export interface ISummaryStats { treeNodeCount: number; blobNodeCount: number; @@ -24,8 +27,19 @@ export interface ISummaryStats { unreferencedBlobSize: number; } +/** + * The summarization methods are recursively invoked during the summary calculation and will + * compose the Summary Tree. At the same time, each component that is taking part of the summarization + * will populate the tree information aggregation for its subtree through the ISummaryStats interface. + * Any component that implements IChannelContext, IFluidDataStoreChannel or extends SharedObject + * will be taking part of the summarization process. + */ export interface ISummaryTreeWithStats { + /** Represents an agreggation of node counts and blob sizes associated to the current summary information */ stats: ISummaryStats; + /** Summary defines a recursive data structure that will be will be converted to a snapshot tree and uploaded + * to the backend. + */ summary: ISummaryTree; } From 2d94e945f0e4d52823161b61de81784074c23af6 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 25 May 2022 18:22:57 -0700 Subject: [PATCH 07/14] Updating summary definitions. --- .../lib/protocol-definitions/src/summary.ts | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index a30cc5783a98..6f8ef44aaf1d 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -37,7 +37,8 @@ export namespace SummaryType { export const Tree: Tree = 1 as const; /** - * Binary data to be uploaded to the server. + * Binary data to be uploaded to the server. The data is sent to the drivers + * and each driver will decide how the blob will be uploaded to the server. */ export const Blob: Blob = 2 as const; /** @@ -46,7 +47,9 @@ export namespace SummaryType { export const Handle: Handle = 3 as const; /** - * Handle to blobs uploaded outside of the summary. + * Unique identifier to larger blobs uploaded outside of the summary. + * Ex. DDS has large images or video that will be uploaded by the BlobManager and + * receive an Id that can be used in the summary. */ export const Attachment: Attachment = 4 as const; } @@ -55,16 +58,21 @@ export type SummaryType = SummaryType.Attachment | SummaryType.Blob | SummaryTyp export type SummaryTypeNoHandle = SummaryType.Tree | SummaryType.Blob | SummaryType.Attachment; /** - * Path to a previous summary, that hasn't changed since then. + * Path to a summary tree object from the last uploaded summary indicating the summary object hasn't + * changed since it was uploaded. * To illustrate, if a DataStore did not get any ops since last summary, the framework runtime will use a handle for the - * entire DataStore instead of re-sending the entire subtree. Same concept will be applied for a DDS. - * Notice that handles are optimizations from the Fluid Framework Runtime. + * entire DataStore instead of re-sending the entire subtree. Same concept will be applied for a DDS. + * If a DDS did not receive any ops since the last summary, the ISummary tree for that DDS will not have changed and + * the fluid framework will send that DDS' handle so the server can use that previous handle instead of sending the + * entire DDS. An example of handle would be: '//'. + * Notice that handles are optimizations from the Fluid Framework Runtime and the DDS is not aware of the handles. + * Also, the use of Summary Handles is currently restricted to DataStores and DDS. */ export interface ISummaryHandle { type: SummaryType.Handle; /** - * All Summary types are supported, with the exception of the handles which is NOT supported here. + * Type of Summary Handle all Summary types with the exception of handles (which are NOT supported). */ handleType: SummaryTypeNoHandle; @@ -75,7 +83,7 @@ export interface ISummaryHandle { } /** - * Binary data to be uploaded to the server as part of the document's Summary. + * String or Binary data to be uploaded to the server as part of the document's Summary. */ export interface ISummaryBlob { type: SummaryType.Blob; @@ -93,7 +101,8 @@ export interface ISummaryAttachment { } /** - * Recursive data structure that is composed by leaf nodes of SummaryObject type. + * Tree Node data structure with children that are nodes of SummaryObject type: + * Blob, Handle, Attachment or another Tree. */ export interface ISummaryTree { type: SummaryType.Tree; From 43445267acafad68eafa280963616daa1d28c405 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 25 May 2022 23:46:25 -0700 Subject: [PATCH 08/14] adding missing file --- api-report/runtime-definitions.api.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api-report/runtime-definitions.api.md b/api-report/runtime-definitions.api.md index 42c133ae542d..9cf71bc85686 100644 --- a/api-report/runtime-definitions.api.md +++ b/api-report/runtime-definitions.api.md @@ -354,7 +354,7 @@ export interface ISummarizerNodeWithGC extends ISummarizerNode { updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number): void; } -// @public (undocumented) +// @public export interface ISummaryStats { // (undocumented) blobNodeCount: number; @@ -368,11 +368,9 @@ export interface ISummaryStats { unreferencedBlobSize: number; } -// @public (undocumented) +// @public export interface ISummaryTreeWithStats { - // (undocumented) stats: ISummaryStats; - // (undocumented) summary: ISummaryTree; } From bddf991b54310688125cec154b65e82cd12b9228 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Thu, 26 May 2022 09:52:31 -0700 Subject: [PATCH 09/14] Addressing initial Review comments --- .../lib/protocol-definitions/src/summary.ts | 26 +++++++++---------- .../runtime-definitions/src/summary.ts | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index 6f8ef44aaf1d..ca1e84776f22 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -22,7 +22,7 @@ export interface ISummaryCommitter { } /** - * Represents a leaf node from the Summary Tree. + * Represents a node from the Summary Tree. */ // eslint-disable-next-line @typescript-eslint/no-namespace export namespace SummaryType { @@ -32,17 +32,19 @@ export namespace SummaryType { export type Attachment = 4; /** - * Another recursive data structure. + * Represents a sub-tree in the summary. */ export const Tree: Tree = 1 as const; /** - * Binary data to be uploaded to the server. The data is sent to the drivers - * and each driver will decide how the blob will be uploaded to the server. + * Represents a blob of data that is added to the summary. + * Such as the user data that is added to the DDS or metadata added by runtime + * such as data store / channel attributes. */ export const Blob: Blob = 2 as const; + /** - * Path to an already stored tree that hasn't changed since the last summary. + * Path to a summary tree object from the last successful summary. */ export const Handle: Handle = 3 as const; @@ -58,13 +60,11 @@ export type SummaryType = SummaryType.Attachment | SummaryType.Blob | SummaryTyp export type SummaryTypeNoHandle = SummaryType.Tree | SummaryType.Blob | SummaryType.Attachment; /** - * Path to a summary tree object from the last uploaded summary indicating the summary object hasn't + * Path to a summary tree object from the last successful summary indicating the summary object hasn't * changed since it was uploaded. - * To illustrate, if a DataStore did not get any ops since last summary, the framework runtime will use a handle for the - * entire DataStore instead of re-sending the entire subtree. Same concept will be applied for a DDS. - * If a DDS did not receive any ops since the last summary, the ISummary tree for that DDS will not have changed and - * the fluid framework will send that DDS' handle so the server can use that previous handle instead of sending the - * entire DDS. An example of handle would be: '//'. + * To illustrate, if a DataStore did not change since last summary, the framework runtime will use a handle for the + * entire DataStore instead of re-sending the entire subtree. The same concept applies for a DDS. + * An example of handle would be: '//'. * Notice that handles are optimizations from the Fluid Framework Runtime and the DDS is not aware of the handles. * Also, the use of Summary Handles is currently restricted to DataStores and DDS. */ @@ -72,7 +72,7 @@ export interface ISummaryHandle { type: SummaryType.Handle; /** - * Type of Summary Handle all Summary types with the exception of handles (which are NOT supported). + * Type of Summary Handle (SummaryType.Handle is not supported). */ handleType: SummaryTypeNoHandle; @@ -101,7 +101,7 @@ export interface ISummaryAttachment { } /** - * Tree Node data structure with children that are nodes of SummaryObject type: + * Tree Node data structure with children that are nodes of SummaryObject type: * Blob, Handle, Attachment or another Tree. */ export interface ISummaryTree { diff --git a/packages/runtime/runtime-definitions/src/summary.ts b/packages/runtime/runtime-definitions/src/summary.ts index 39eaced286e2..a14451a319be 100644 --- a/packages/runtime/runtime-definitions/src/summary.ts +++ b/packages/runtime/runtime-definitions/src/summary.ts @@ -37,7 +37,8 @@ export interface ISummaryStats { export interface ISummaryTreeWithStats { /** Represents an agreggation of node counts and blob sizes associated to the current summary information */ stats: ISummaryStats; - /** Summary defines a recursive data structure that will be will be converted to a snapshot tree and uploaded + /** + * Summary defines a recursive data structure that will be converted to a snapshot tree and uploaded * to the backend. */ summary: ISummaryTree; From 2e667eb951ebffc6b3b048d4901cb0379e9e7c02 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Thu, 26 May 2022 09:53:14 -0700 Subject: [PATCH 10/14] Address review comments --- common/lib/protocol-definitions/src/summary.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index ca1e84776f22..3de718c3036b 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -66,7 +66,6 @@ export type SummaryTypeNoHandle = SummaryType.Tree | SummaryType.Blob | SummaryT * entire DataStore instead of re-sending the entire subtree. The same concept applies for a DDS. * An example of handle would be: '//'. * Notice that handles are optimizations from the Fluid Framework Runtime and the DDS is not aware of the handles. - * Also, the use of Summary Handles is currently restricted to DataStores and DDS. */ export interface ISummaryHandle { type: SummaryType.Handle; From f5ba6d2386d33a7b6b5713d748c07232894c2a22 Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Fri, 27 May 2022 12:07:58 -0700 Subject: [PATCH 11/14] Addressing review comments. --- .../lib/protocol-definitions/src/summary.ts | 17 ++++++++----- .../datastore-definitions/src/channel.ts | 22 ++++++++++++---- .../runtime-definitions/src/summary.ts | 25 ++++++++++++++----- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index 3de718c3036b..7433e789fa9c 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -22,7 +22,7 @@ export interface ISummaryCommitter { } /** - * Represents a node from the Summary Tree. + * Type tag used to distinguish different types of nodes in a {@link ISummaryTree}. */ // eslint-disable-next-line @typescript-eslint/no-namespace export namespace SummaryType { @@ -65,7 +65,6 @@ export type SummaryTypeNoHandle = SummaryType.Tree | SummaryType.Blob | SummaryT * To illustrate, if a DataStore did not change since last summary, the framework runtime will use a handle for the * entire DataStore instead of re-sending the entire subtree. The same concept applies for a DDS. * An example of handle would be: '//'. - * Notice that handles are optimizations from the Fluid Framework Runtime and the DDS is not aware of the handles. */ export interface ISummaryHandle { type: SummaryType.Handle; @@ -76,13 +75,17 @@ export interface ISummaryHandle { handleType: SummaryTypeNoHandle; /** - * Unique path that identifies the stored handle reference. + * Unique path that identifies the corresponding sub-tree in a previous summary. */ handle: string; } /** - * String or Binary data to be uploaded to the server as part of the document's Summary. + * String or Binary data to be uploaded to the server as part of the container's Summary. + * Note: Already uploaded blobs would be referenced by a ISummaryAttachment. + * Additional information can be found here: https://github.com/microsoft/FluidFramework/issues/6568 + * Ex. "content": "{ \"pkg\":\"[\\\"OfficeRootComponent\\\",\\\"LastEditedComponent\\\"]\", + * \"summaryFormatVersion\":2,\"isRootDataStore\":false }" */ export interface ISummaryBlob { type: SummaryType.Blob; @@ -90,9 +93,11 @@ export interface ISummaryBlob { } /** - * Handle to blobs uploaded outside of the summary. Attachment Blobs are uploaded and downloaded separately via - * http requests and are not included on the snapshot payload. The ISummaryAttachment are handles to these blobs. + * Unique identifier for blobs uploaded outside of the summary. Attachment Blobs are uploaded and + * downloaded separately and do not take part of the snapshot payload. + * The id gets returned from the backend after the attachment has been uploaded. * Additional information can be found here: https://github.com/microsoft/FluidFramework/issues/6374 + * Ex. "id": "bQAQKARDdMdTgqICmBa_ZB86YXwGP" */ export interface ISummaryAttachment { type: SummaryType.Attachment; diff --git a/packages/runtime/datastore-definitions/src/channel.ts b/packages/runtime/datastore-definitions/src/channel.ts index fbf33449f0c4..e45c04959d54 100644 --- a/packages/runtime/datastore-definitions/src/channel.ts +++ b/packages/runtime/datastore-definitions/src/channel.ts @@ -20,11 +20,22 @@ export interface IChannel extends IFluidLoadable { readonly attributes: IChannelAttributes; /** - * Generates summary of the channel synchronously. + * Generates summary of the channel synchronously. It is called when an `attach message` + * for a local channel is generated. In other words, when the channel is being attached + * to make it visible to other clients. + * Note: Since Attach Summary is generated for local channels when making them visible to + * remote clients, they don't have any previous summaries to compare against. For this reason, + * The attach summary cannot contain summary handles (paths to sub-trees or blobs). + * It can, however, contain ISummaryAttachment (handles to blobs uploaded async via the blob manager). * @param fullTree - flag indicating whether the attempt should generate a full * summary tree without any handles for unchanged subtrees. - * @param trackState - This tells whether we should track state from this summary. - * @returns A tree representing the summary of the channel. + * @param trackState - optimization for tracking state of objects across summaries. If the state + * of an object did not change since last successful summary, an ISummaryHandle can be used + * instead of re-summarizing it. If this is false, the expectation is that you should never + * send an ISummaryHandle since you are not expected to track state. + * Note: The goal is to remove the trackState and automatically decided whether the + * handles will be used or not: https://github.com/microsoft/FluidFramework/issues/10455 + * @returns A summary capturing the current state of the channel. */ getAttachSummary(fullTree?: boolean, trackState?: boolean): ISummaryTreeWithStats; @@ -32,9 +43,10 @@ export interface IChannel extends IFluidLoadable { * Generates summary of the channel asynchronously. * This should not be called where the channel can be modified while summarization is in progress. * @param fullTree - flag indicating whether the attempt should generate a full - * summary tree without any handles for unchanged subtrees. + * summary tree without any handles for unchanged subtrees. It is only set to true when generating + * a summary from the entire container. * @param trackState - This tells whether we should track state from this summary. - * @returns A tree representing the summary of the channel. + * @returns A summary capturing the current state of the channel. */ summarize(fullTree?: boolean, trackState?: boolean): Promise; diff --git a/packages/runtime/runtime-definitions/src/summary.ts b/packages/runtime/runtime-definitions/src/summary.ts index a14451a319be..9aeb04ccd12f 100644 --- a/packages/runtime/runtime-definitions/src/summary.ts +++ b/packages/runtime/runtime-definitions/src/summary.ts @@ -28,27 +28,40 @@ export interface ISummaryStats { } /** - * The summarization methods are recursively invoked during the summary calculation and will - * compose the Summary Tree. At the same time, each component that is taking part of the summarization - * will populate the tree information aggregation for its subtree through the ISummaryStats interface. - * Any component that implements IChannelContext, IFluidDataStoreChannel or extends SharedObject - * will be taking part of the summarization process. + * Represents the summary tree for a node along with the statistics for that tree. + * For example, for a given data store, it contains the data for data store along with a subtree for + * each of its DDS. + * Any component that implements IChannelContext, IFluidDataStoreChannel or extends SharedObject + * will be taking part of the summarization process. */ export interface ISummaryTreeWithStats { /** Represents an agreggation of node counts and blob sizes associated to the current summary information */ stats: ISummaryStats; /** - * Summary defines a recursive data structure that will be converted to a snapshot tree and uploaded + * A recursive data structure that will be converted to a snapshot tree and uploaded * to the backend. */ summary: ISummaryTree; } +/** + * Represents a summary at a current sequence number. + */ export interface ISummarizeResult { stats: ISummaryStats; summary: SummaryTree; } +/** + * Contains the same data as ISummaryResult but in order to avoid naming colisions, + * the data store summaries are wrapped around an array of labels identified by pathPartsForChildren. + * Ex: id:"" + pathPartsForChildren: ["path1"] + stats: ... + summary: + ... + "path1": + */ export interface ISummarizeInternalResult extends ISummarizeResult { id: string; /** Additional path parts between this node's ID and its children's IDs. */ From 0e699799b7e184bc99439be96e0bcea387f7f27f Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Tue, 31 May 2022 11:53:35 -0700 Subject: [PATCH 12/14] Documentation needs to be updated. --- api-report/runtime-definitions.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api-report/runtime-definitions.api.md b/api-report/runtime-definitions.api.md index 9cf71bc85686..9cbee2977f01 100644 --- a/api-report/runtime-definitions.api.md +++ b/api-report/runtime-definitions.api.md @@ -286,14 +286,14 @@ export interface ISignalEnvelope { }; } -// @public (undocumented) +// @public export interface ISummarizeInternalResult extends ISummarizeResult { // (undocumented) id: string; pathPartsForChildren?: string[]; } -// @public (undocumented) +// @public export interface ISummarizeResult { // (undocumented) stats: ISummaryStats; From e0bc62e9eef118da28f651822c2c8d855ede1ddb Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Tue, 31 May 2022 16:42:18 -0700 Subject: [PATCH 13/14] fix npm run lint --- common/lib/protocol-definitions/src/summary.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/protocol-definitions/src/summary.ts b/common/lib/protocol-definitions/src/summary.ts index 7433e789fa9c..bae9656b050c 100644 --- a/common/lib/protocol-definitions/src/summary.ts +++ b/common/lib/protocol-definitions/src/summary.ts @@ -84,8 +84,8 @@ export interface ISummaryHandle { * String or Binary data to be uploaded to the server as part of the container's Summary. * Note: Already uploaded blobs would be referenced by a ISummaryAttachment. * Additional information can be found here: https://github.com/microsoft/FluidFramework/issues/6568 - * Ex. "content": "{ \"pkg\":\"[\\\"OfficeRootComponent\\\",\\\"LastEditedComponent\\\"]\", - * \"summaryFormatVersion\":2,\"isRootDataStore\":false }" + * Ex. "content": "\{ \"pkg\":\"[\\\"OfficeRootComponent\\\",\\\"LastEditedComponent\\\"]\", + * \"summaryFormatVersion\":2,\"isRootDataStore\":false \}" */ export interface ISummaryBlob { type: SummaryType.Blob; From 7411adc0ac67115e217dd9fc88bade352238a56a Mon Sep 17 00:00:00 2001 From: Nicholas Couri Date: Wed, 1 Jun 2022 11:31:37 -0700 Subject: [PATCH 14/14] updating .md --- .../api-report/protocol-definitions.api.md | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/common/lib/protocol-definitions/api-report/protocol-definitions.api.md b/common/lib/protocol-definitions/api-report/protocol-definitions.api.md index a192248bb03a..77d7d3f3aa63 100644 --- a/common/lib/protocol-definitions/api-report/protocol-definitions.api.md +++ b/common/lib/protocol-definitions/api-report/protocol-definitions.api.md @@ -408,7 +408,7 @@ export interface ISummaryAck { summaryProposal: ISummaryProposal; } -// @public (undocumented) +// @public export interface ISummaryAttachment { // (undocumented) id: string; @@ -426,7 +426,7 @@ export interface ISummaryAuthor { name: string; } -// @public (undocumented) +// @public export interface ISummaryBlob { // (undocumented) content: string | Uint8Array; @@ -472,11 +472,9 @@ export interface ISummaryContent { parents: string[]; } -// @public (undocumented) +// @public export interface ISummaryHandle { - // (undocumented) handle: string; - // (undocumented) handleType: SummaryTypeNoHandle; // (undocumented) type: SummaryType.Handle; @@ -505,7 +503,7 @@ export interface ISummaryTokenClaims { sub: string; } -// @public (undocumented) +// @public export interface ISummaryTree { // (undocumented) tree: { @@ -664,7 +662,7 @@ export type SummaryObject = ISummaryTree | ISummaryBlob | ISummaryHandle | ISumm // @public (undocumented) export type SummaryTree = ISummaryTree | ISummaryHandle; -// @public (undocumented) +// @public export namespace SummaryType { // (undocumented) export type Attachment = 4; @@ -674,14 +672,10 @@ export namespace SummaryType { export type Handle = 3; // (undocumented) export type Tree = 1; - const // (undocumented) - Tree: Tree; - const // (undocumented) - Blob: Blob; - const // (undocumented) - Handle: Handle; - const // (undocumented) - Attachment: Attachment; + const Tree: Tree; + const Blob: Blob; + const Handle: Handle; + const Attachment: Attachment; } // @public (undocumented)