Skip to content

Commit

Permalink
Added Diagnostic type definitions for @azure/cosmos (Azure#25103)
Browse files Browse the repository at this point in the history
### Packages impacted by this PR
@azure/cosmos

### Issues associated with this PR
[Azure#21177](Azure#21177)

### Describe the problem that is addressed by this PR
This PR is the first in series, for adding Diagnostics in @azure/comsos
package.
Here is the [design
doc](https://gist.github.com/v1k1/8950942027c9ca1bf631bb6d135c7451).
Here is the [complete reference
PR](Azure#25043), but to make
review easy, I have broken the PR into 3 parts. (The reference PR is not
to be merged; it is simply to showcase how all things will tie together)
1. Type Definitions.
2. Adding Diagnostics to point lookup and exception APIs
3. Adding diagnostics to bulk/batch and query APIs

### What are the possible designs available to address the problem? If
there are more than one possible design, why was the one in this PR
chosen?


### Are there test cases added in this PR? _(If not, why?)_
No, this PR contains only type definitions.

### Provide a list of related PRs _(if any)_
Azure#25043

### Command used to generate this PR:**_(Applicable only to SDK release
request PRs)_

### Checklists
- [ ] Added impacted package name to the issue description
- [ ] Does this PR needs any fixes in the SDK Generator?** _(If so,
create an Issue in the
[Autorest/typescript](https://github.com/Azure/autorest.typescript)
repository and link it here)_
- [ ] Added a changelog (if necessary)
  • Loading branch information
v1k1 authored Apr 14, 2023
1 parent d2ccb73 commit 7588664
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 0 deletions.
70 changes: 70 additions & 0 deletions sdk/cosmosdb/cosmos/review/cosmos.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,18 @@ export class ClientSideMetrics {
static readonly zero: ClientSideMetrics;
}

// @public
export type ClientSideRequestStatistics = {
requestStartTimeUTCInMs: number;
requestEndTimeUTCInMs: number;
activityId: string;
locationEndpointsContacted: Location_2[];
retryDiagnostics: RetryDiagnostics;
metadataDiagnostics: MetadataLookUpDiagnostics;
requestPayloadLength: number;
responsePayloadLength: number;
};

// @public
export class Conflict {
constructor(container: Container, id: string, clientContext: ClientContext, partitionKey?: PartitionKey);
Expand Down Expand Up @@ -567,6 +579,16 @@ export interface CosmosClientOptions {
userAgentSuffix?: string;
}

// @public
export class CosmosDiagnostics {
// (undocumented)
readonly clientSideRequestStatistics: ClientSideRequestStatistics;
// (undocumented)
get getContactedRegionNames(): Set<string>;
// (undocumented)
readonly id: string;
}

// @public (undocumented)
export interface CosmosHeaders {
// (undocumented)
Expand Down Expand Up @@ -743,6 +765,20 @@ export type ExistingKeyOperation = {
// @public (undocumented)
export function extractPartitionKey(document: unknown, partitionKeyDefinition: PartitionKeyDefinition): PartitionKey[];

// @public
export interface FailedRequestAttemptDiagnostic {
// (undocumented)
attemptNumber: number;
// (undocumented)
endTimeUTCInMs: number;
// (undocumented)
id: string;
// (undocumented)
startTimeUTCInMs: number;
// (undocumented)
statusCode: string;
}

// @public
export interface FeedOptions extends SharedOptions {
accessCondition?: {
Expand Down Expand Up @@ -960,6 +996,35 @@ interface Location_2 {
}
export { Location_2 as Location }

// @public
export interface MetadataLookUpDiagnostic {
// (undocumented)
activityId: string;
// (undocumented)
endTimeUTCInMs: number;
// (undocumented)
id: string;
// (undocumented)
metaDataType: MetadataLookUpType;
// (undocumented)
startTimeUTCInMs: number;
}

// @public
export type MetadataLookUpDiagnostics = {
metadataLookups: MetadataLookUpDiagnostic[];
};

// @public
export enum MetadataLookUpType {
// (undocumented)
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
// (undocumented)
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
// (undocumented)
ServerAddressLookUp = "SERVER_ADDRESS_LOOK_UP"
}

// @public
export type Next<T> = (context: RequestContext) => Promise<Response_2<T>>;

Expand Down Expand Up @@ -1571,6 +1636,11 @@ export { Response_2 as Response }

export { RestError }

// @public
export type RetryDiagnostics = {
failedAttempts: FailedRequestAttemptDiagnostic[];
};

// @public
export interface RetryOptions {
fixedRetryIntervalInMilliseconds: number;
Expand Down
146 changes: 146 additions & 0 deletions sdk/cosmosdb/cosmos/src/CosmosDiagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { v4 } from "uuid";
import { Location } from "./documents";
import { CosmosDiagnosticContext } from "./CosmosDiagnosticsContext";

/**
* This is a Cosmos Diagnostic type that holds diagnostic information during client operations. ie. Item.read().
* The `clientSideRequestStatistics` member contains diagnostic information on operations happening in client process. ie.e
* - metadata lookups.
* - retries
* - endpoints contacted.
* - request, response payload stats.
* Here all the server requests, apart from the final intended resource are considered as metadata calls.
* i.e. for item.read(id), if the client makes server call to discover endpoints it would be considered
* as metadata call.
*/
export class CosmosDiagnostics {
/**
* @internal
*/
constructor(
public readonly id: string,
clientSideRequestStatistics: ClientSideRequestStatistics,
private readonly cosmosDiagnosticContext: CosmosDiagnosticContext
) {
this.clientSideRequestStatistics = clientSideRequestStatistics;
}

public readonly clientSideRequestStatistics: ClientSideRequestStatistics;

public get getContactedRegionNames(): Set<string> {
const locations = this.cosmosDiagnosticContext.locationEndpointsContacted.values();
return new Set([...locations].map((location) => location.name));
}
}

/**
* This type contains diagnostic information regarding all metadata request to server during an CosmosDB client operation.
*/
export type MetadataLookUpDiagnostics = {
metadataLookups: MetadataLookUpDiagnostic[];
};

/**
* This type captures diagnostic information regarding retries attempt during an CosmosDB client operation.
*/
export type RetryDiagnostics = {
failedAttempts: FailedRequestAttemptDiagnostic[];
};

/**
* This type contains diagnostic information regarding a single metadata request to server.
*/
export interface MetadataLookUpDiagnostic {
id: string;
startTimeUTCInMs: number;
endTimeUTCInMs: number;
metaDataType: MetadataLookUpType;
activityId: string;
}

/**
* This type captures diagnostic information regarding a failed request to server api.
*/
export interface FailedRequestAttemptDiagnostic {
id: string;
attemptNumber: number;
startTimeUTCInMs: number;
endTimeUTCInMs: number;
statusCode: string;
}

/**
* This is enum for Type of Metadata lookups possible.
*/
export enum MetadataLookUpType {
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
ServerAddressLookUp = "SERVER_ADDRESS_LOOK_UP",
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
}

/**
* This is a collection type for all client side diagnostic information.
*/
export type ClientSideRequestStatistics = {
/**
* This is the UTC timestamp for start of client operation.
*/
requestStartTimeUTCInMs: number;
/**
* This is the UTC timestamp for end of client operation.
*/
requestEndTimeUTCInMs: number;
/**
* This is the activityId for request, made to server for fetching the requested resource. (As opposed to other potential meta data requests)
*/
activityId: string;
/**
* This is the list of Location Endpoints contacted during the client operation.
*/
locationEndpointsContacted: Location[];
/**
* This field captures diagnostic information for retries happened during client operation.
*/
retryDiagnostics: RetryDiagnostics;
/**
* This field captures diagnostic information for meta data lookups happened during client operation.
*/
metadataDiagnostics: MetadataLookUpDiagnostics;
/**
* This is the payload length, in bytes made to server for the client operation requested. (This doesn't include payloads for meta data requests)
*/
requestPayloadLength: number;
/**
* This is the payload length, in bytes made to recieved from server for the client operation requested. (This doesn't include payloads for meta data responses)
*/
responsePayloadLength: number;
};

/**
* @hidden
* Utility function to create an Empty CosmosDiagnostic object.
* @returns
*/
export function getEmptyCosmosDiagnostics(): CosmosDiagnostics {
return new CosmosDiagnostics(
v4(),
{
activityId: "",
requestEndTimeUTCInMs: 0,
requestStartTimeUTCInMs: 0,
locationEndpointsContacted: [],
retryDiagnostics: {
failedAttempts: [],
},
metadataDiagnostics: {
metadataLookups: [],
},
requestPayloadLength: 0,
responsePayloadLength: 0,
},
new CosmosDiagnosticContext()
);
}
136 changes: 136 additions & 0 deletions sdk/cosmosdb/cosmos/src/CosmosDiagnosticsContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { v4 } from "uuid";
import { Constants } from "./common";
import { Location } from "./documents";
import { CosmosHeaders } from "./queryExecutionContext";
import {
CosmosDiagnostics,
FailedRequestAttemptDiagnostic,
MetadataLookUpDiagnostic,
MetadataLookUpType,
} from "./CosmosDiagnostics";

/**
* @hidden
* Internal class to hold CosmosDiagnostic information all through the lifecycle of a request.
* This object gathers diagnostic information throughout Client operation which may span across multiple
* Server call, retries etc.
* Functions - recordFailedAttempt, recordMetaDataQuery, recordEndpointContactEvent are used to ingest
* data into the context. At the end of operation, getDiagnostics() is used to
* get final CosmosDiagnostic object.
*/
export class CosmosDiagnosticContext {
private requestStartTimeUTCinMs: number;
private requestEndTimeUTCinMs: number;
private retryStartTimeUTCinMs: number;
private headers: CosmosHeaders = {};
private retryAttempNumber: number;
private failedAttempts: FailedRequestAttemptDiagnostic[] = [];
private metadataLookups: MetadataLookUpDiagnostic[] = [];
private requestPayloadLength: number;
private responsePayloadLength: number;

public locationEndpointsContacted: Map<string, Location> = new Map();

public constructor() {
this.requestStartTimeUTCinMs = getCurrentTimestampInMs();
this.retryStartTimeUTCinMs = this.requestStartTimeUTCinMs;
}

public recordRequestPayload(payload: string): void {
this.requestPayloadLength = payload.length;
}

public recordResponseStats(payload: string, headers: CosmosHeaders): void {
this.responsePayloadLength = typeof payload === "string" ? payload.length : 0;
this.headers = { ...this.headers, ...headers };
}

public recordFailedAttempt(statusCode: string): void {
const attempt: FailedRequestAttemptDiagnostic = {
id: v4(),
attemptNumber: this.retryAttempNumber,
startTimeUTCInMs: this.retryStartTimeUTCinMs,
endTimeUTCInMs: getCurrentTimestampInMs(),
statusCode,
};
this.retryStartTimeUTCinMs = getCurrentTimestampInMs();
this.retryAttempNumber++;
this.failedAttempts.push(attempt);
}

public recordMetaDataLookup(
diagnostics: CosmosDiagnostics,
metaDataType: MetadataLookUpType
): void {
const metaDataRequest = {
startTimeUTCInMs: diagnostics.clientSideRequestStatistics.requestStartTimeUTCInMs,
endTimeUTCInMs: diagnostics.clientSideRequestStatistics.requestEndTimeUTCInMs,
activityId: diagnostics.clientSideRequestStatistics.activityId,
metaDataType,
id: v4(),
};
this.metadataLookups.push(metaDataRequest);
}

// public recordSerializationEvent() {}

// public recordResponse() {}

public mergeDiagnostics(diagnostics: CosmosDiagnostics): void {
diagnostics.clientSideRequestStatistics.locationEndpointsContacted.forEach((endpoint) =>
this.locationEndpointsContacted.set(endpoint.databaseAccountEndpoint, endpoint)
);
diagnostics.clientSideRequestStatistics.metadataDiagnostics.metadataLookups.forEach((lookup) =>
this.metadataLookups.push(lookup)
);
diagnostics.clientSideRequestStatistics.retryDiagnostics.failedAttempts.forEach((lookup) =>
this.failedAttempts.push(lookup)
);
}

public recordEndpointContactEvent(location: Location): void {
this.locationEndpointsContacted.set(location.databaseAccountEndpoint, location);
}

public getDiagnostics(): CosmosDiagnostics {
this.recordSessionEnd();
return new CosmosDiagnostics(
v4(),
{
activityId: this.getActivityId(),
requestStartTimeUTCInMs: this.requestStartTimeUTCinMs,
requestEndTimeUTCInMs: this.requestEndTimeUTCinMs,
locationEndpointsContacted: [...this.locationEndpointsContacted.values()],
metadataDiagnostics: {
metadataLookups: [...this.metadataLookups],
},
retryDiagnostics: {
failedAttempts: [...this.failedAttempts],
},
requestPayloadLength: this.requestPayloadLength,
responsePayloadLength: this.responsePayloadLength,
},
this
);
}

private recordSessionEnd() {
this.requestEndTimeUTCinMs = getCurrentTimestampInMs();
}

private getActivityId(): string {
return this.headers[Constants.HttpHeaders.ActivityId] as string;
}
}

/**
* @hidden
* Utility function to get currentTime in UTC milliseconds.
* @returns
*/
export function getCurrentTimestampInMs(): number {
return Date.now();
}
Loading

0 comments on commit 7588664

Please sign in to comment.