Skip to content

Commit 7588664

Browse files
authored
Added Diagnostic type definitions for @azure/cosmos (Azure#25103)
### 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)
1 parent d2ccb73 commit 7588664

File tree

5 files changed

+365
-0
lines changed

5 files changed

+365
-0
lines changed

sdk/cosmosdb/cosmos/review/cosmos.api.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ export class ClientSideMetrics {
209209
static readonly zero: ClientSideMetrics;
210210
}
211211

212+
// @public
213+
export type ClientSideRequestStatistics = {
214+
requestStartTimeUTCInMs: number;
215+
requestEndTimeUTCInMs: number;
216+
activityId: string;
217+
locationEndpointsContacted: Location_2[];
218+
retryDiagnostics: RetryDiagnostics;
219+
metadataDiagnostics: MetadataLookUpDiagnostics;
220+
requestPayloadLength: number;
221+
responsePayloadLength: number;
222+
};
223+
212224
// @public
213225
export class Conflict {
214226
constructor(container: Container, id: string, clientContext: ClientContext, partitionKey?: PartitionKey);
@@ -567,6 +579,16 @@ export interface CosmosClientOptions {
567579
userAgentSuffix?: string;
568580
}
569581

582+
// @public
583+
export class CosmosDiagnostics {
584+
// (undocumented)
585+
readonly clientSideRequestStatistics: ClientSideRequestStatistics;
586+
// (undocumented)
587+
get getContactedRegionNames(): Set<string>;
588+
// (undocumented)
589+
readonly id: string;
590+
}
591+
570592
// @public (undocumented)
571593
export interface CosmosHeaders {
572594
// (undocumented)
@@ -743,6 +765,20 @@ export type ExistingKeyOperation = {
743765
// @public (undocumented)
744766
export function extractPartitionKey(document: unknown, partitionKeyDefinition: PartitionKeyDefinition): PartitionKey[];
745767

768+
// @public
769+
export interface FailedRequestAttemptDiagnostic {
770+
// (undocumented)
771+
attemptNumber: number;
772+
// (undocumented)
773+
endTimeUTCInMs: number;
774+
// (undocumented)
775+
id: string;
776+
// (undocumented)
777+
startTimeUTCInMs: number;
778+
// (undocumented)
779+
statusCode: string;
780+
}
781+
746782
// @public
747783
export interface FeedOptions extends SharedOptions {
748784
accessCondition?: {
@@ -960,6 +996,35 @@ interface Location_2 {
960996
}
961997
export { Location_2 as Location }
962998

999+
// @public
1000+
export interface MetadataLookUpDiagnostic {
1001+
// (undocumented)
1002+
activityId: string;
1003+
// (undocumented)
1004+
endTimeUTCInMs: number;
1005+
// (undocumented)
1006+
id: string;
1007+
// (undocumented)
1008+
metaDataType: MetadataLookUpType;
1009+
// (undocumented)
1010+
startTimeUTCInMs: number;
1011+
}
1012+
1013+
// @public
1014+
export type MetadataLookUpDiagnostics = {
1015+
metadataLookups: MetadataLookUpDiagnostic[];
1016+
};
1017+
1018+
// @public
1019+
export enum MetadataLookUpType {
1020+
// (undocumented)
1021+
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
1022+
// (undocumented)
1023+
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
1024+
// (undocumented)
1025+
ServerAddressLookUp = "SERVER_ADDRESS_LOOK_UP"
1026+
}
1027+
9631028
// @public
9641029
export type Next<T> = (context: RequestContext) => Promise<Response_2<T>>;
9651030

@@ -1571,6 +1636,11 @@ export { Response_2 as Response }
15711636

15721637
export { RestError }
15731638

1639+
// @public
1640+
export type RetryDiagnostics = {
1641+
failedAttempts: FailedRequestAttemptDiagnostic[];
1642+
};
1643+
15741644
// @public
15751645
export interface RetryOptions {
15761646
fixedRetryIntervalInMilliseconds: number;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import { v4 } from "uuid";
5+
import { Location } from "./documents";
6+
import { CosmosDiagnosticContext } from "./CosmosDiagnosticsContext";
7+
8+
/**
9+
* This is a Cosmos Diagnostic type that holds diagnostic information during client operations. ie. Item.read().
10+
* The `clientSideRequestStatistics` member contains diagnostic information on operations happening in client process. ie.e
11+
* - metadata lookups.
12+
* - retries
13+
* - endpoints contacted.
14+
* - request, response payload stats.
15+
* Here all the server requests, apart from the final intended resource are considered as metadata calls.
16+
* i.e. for item.read(id), if the client makes server call to discover endpoints it would be considered
17+
* as metadata call.
18+
*/
19+
export class CosmosDiagnostics {
20+
/**
21+
* @internal
22+
*/
23+
constructor(
24+
public readonly id: string,
25+
clientSideRequestStatistics: ClientSideRequestStatistics,
26+
private readonly cosmosDiagnosticContext: CosmosDiagnosticContext
27+
) {
28+
this.clientSideRequestStatistics = clientSideRequestStatistics;
29+
}
30+
31+
public readonly clientSideRequestStatistics: ClientSideRequestStatistics;
32+
33+
public get getContactedRegionNames(): Set<string> {
34+
const locations = this.cosmosDiagnosticContext.locationEndpointsContacted.values();
35+
return new Set([...locations].map((location) => location.name));
36+
}
37+
}
38+
39+
/**
40+
* This type contains diagnostic information regarding all metadata request to server during an CosmosDB client operation.
41+
*/
42+
export type MetadataLookUpDiagnostics = {
43+
metadataLookups: MetadataLookUpDiagnostic[];
44+
};
45+
46+
/**
47+
* This type captures diagnostic information regarding retries attempt during an CosmosDB client operation.
48+
*/
49+
export type RetryDiagnostics = {
50+
failedAttempts: FailedRequestAttemptDiagnostic[];
51+
};
52+
53+
/**
54+
* This type contains diagnostic information regarding a single metadata request to server.
55+
*/
56+
export interface MetadataLookUpDiagnostic {
57+
id: string;
58+
startTimeUTCInMs: number;
59+
endTimeUTCInMs: number;
60+
metaDataType: MetadataLookUpType;
61+
activityId: string;
62+
}
63+
64+
/**
65+
* This type captures diagnostic information regarding a failed request to server api.
66+
*/
67+
export interface FailedRequestAttemptDiagnostic {
68+
id: string;
69+
attemptNumber: number;
70+
startTimeUTCInMs: number;
71+
endTimeUTCInMs: number;
72+
statusCode: string;
73+
}
74+
75+
/**
76+
* This is enum for Type of Metadata lookups possible.
77+
*/
78+
export enum MetadataLookUpType {
79+
PartitionKeyRangeLookUp = "PARTITION_KEY_RANGE_LOOK_UP",
80+
ServerAddressLookUp = "SERVER_ADDRESS_LOOK_UP",
81+
DatabaseAccountLookUp = "DATABASE_ACCOUNT_LOOK_UP",
82+
}
83+
84+
/**
85+
* This is a collection type for all client side diagnostic information.
86+
*/
87+
export type ClientSideRequestStatistics = {
88+
/**
89+
* This is the UTC timestamp for start of client operation.
90+
*/
91+
requestStartTimeUTCInMs: number;
92+
/**
93+
* This is the UTC timestamp for end of client operation.
94+
*/
95+
requestEndTimeUTCInMs: number;
96+
/**
97+
* This is the activityId for request, made to server for fetching the requested resource. (As opposed to other potential meta data requests)
98+
*/
99+
activityId: string;
100+
/**
101+
* This is the list of Location Endpoints contacted during the client operation.
102+
*/
103+
locationEndpointsContacted: Location[];
104+
/**
105+
* This field captures diagnostic information for retries happened during client operation.
106+
*/
107+
retryDiagnostics: RetryDiagnostics;
108+
/**
109+
* This field captures diagnostic information for meta data lookups happened during client operation.
110+
*/
111+
metadataDiagnostics: MetadataLookUpDiagnostics;
112+
/**
113+
* This is the payload length, in bytes made to server for the client operation requested. (This doesn't include payloads for meta data requests)
114+
*/
115+
requestPayloadLength: number;
116+
/**
117+
* 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)
118+
*/
119+
responsePayloadLength: number;
120+
};
121+
122+
/**
123+
* @hidden
124+
* Utility function to create an Empty CosmosDiagnostic object.
125+
* @returns
126+
*/
127+
export function getEmptyCosmosDiagnostics(): CosmosDiagnostics {
128+
return new CosmosDiagnostics(
129+
v4(),
130+
{
131+
activityId: "",
132+
requestEndTimeUTCInMs: 0,
133+
requestStartTimeUTCInMs: 0,
134+
locationEndpointsContacted: [],
135+
retryDiagnostics: {
136+
failedAttempts: [],
137+
},
138+
metadataDiagnostics: {
139+
metadataLookups: [],
140+
},
141+
requestPayloadLength: 0,
142+
responsePayloadLength: 0,
143+
},
144+
new CosmosDiagnosticContext()
145+
);
146+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import { v4 } from "uuid";
5+
import { Constants } from "./common";
6+
import { Location } from "./documents";
7+
import { CosmosHeaders } from "./queryExecutionContext";
8+
import {
9+
CosmosDiagnostics,
10+
FailedRequestAttemptDiagnostic,
11+
MetadataLookUpDiagnostic,
12+
MetadataLookUpType,
13+
} from "./CosmosDiagnostics";
14+
15+
/**
16+
* @hidden
17+
* Internal class to hold CosmosDiagnostic information all through the lifecycle of a request.
18+
* This object gathers diagnostic information throughout Client operation which may span across multiple
19+
* Server call, retries etc.
20+
* Functions - recordFailedAttempt, recordMetaDataQuery, recordEndpointContactEvent are used to ingest
21+
* data into the context. At the end of operation, getDiagnostics() is used to
22+
* get final CosmosDiagnostic object.
23+
*/
24+
export class CosmosDiagnosticContext {
25+
private requestStartTimeUTCinMs: number;
26+
private requestEndTimeUTCinMs: number;
27+
private retryStartTimeUTCinMs: number;
28+
private headers: CosmosHeaders = {};
29+
private retryAttempNumber: number;
30+
private failedAttempts: FailedRequestAttemptDiagnostic[] = [];
31+
private metadataLookups: MetadataLookUpDiagnostic[] = [];
32+
private requestPayloadLength: number;
33+
private responsePayloadLength: number;
34+
35+
public locationEndpointsContacted: Map<string, Location> = new Map();
36+
37+
public constructor() {
38+
this.requestStartTimeUTCinMs = getCurrentTimestampInMs();
39+
this.retryStartTimeUTCinMs = this.requestStartTimeUTCinMs;
40+
}
41+
42+
public recordRequestPayload(payload: string): void {
43+
this.requestPayloadLength = payload.length;
44+
}
45+
46+
public recordResponseStats(payload: string, headers: CosmosHeaders): void {
47+
this.responsePayloadLength = typeof payload === "string" ? payload.length : 0;
48+
this.headers = { ...this.headers, ...headers };
49+
}
50+
51+
public recordFailedAttempt(statusCode: string): void {
52+
const attempt: FailedRequestAttemptDiagnostic = {
53+
id: v4(),
54+
attemptNumber: this.retryAttempNumber,
55+
startTimeUTCInMs: this.retryStartTimeUTCinMs,
56+
endTimeUTCInMs: getCurrentTimestampInMs(),
57+
statusCode,
58+
};
59+
this.retryStartTimeUTCinMs = getCurrentTimestampInMs();
60+
this.retryAttempNumber++;
61+
this.failedAttempts.push(attempt);
62+
}
63+
64+
public recordMetaDataLookup(
65+
diagnostics: CosmosDiagnostics,
66+
metaDataType: MetadataLookUpType
67+
): void {
68+
const metaDataRequest = {
69+
startTimeUTCInMs: diagnostics.clientSideRequestStatistics.requestStartTimeUTCInMs,
70+
endTimeUTCInMs: diagnostics.clientSideRequestStatistics.requestEndTimeUTCInMs,
71+
activityId: diagnostics.clientSideRequestStatistics.activityId,
72+
metaDataType,
73+
id: v4(),
74+
};
75+
this.metadataLookups.push(metaDataRequest);
76+
}
77+
78+
// public recordSerializationEvent() {}
79+
80+
// public recordResponse() {}
81+
82+
public mergeDiagnostics(diagnostics: CosmosDiagnostics): void {
83+
diagnostics.clientSideRequestStatistics.locationEndpointsContacted.forEach((endpoint) =>
84+
this.locationEndpointsContacted.set(endpoint.databaseAccountEndpoint, endpoint)
85+
);
86+
diagnostics.clientSideRequestStatistics.metadataDiagnostics.metadataLookups.forEach((lookup) =>
87+
this.metadataLookups.push(lookup)
88+
);
89+
diagnostics.clientSideRequestStatistics.retryDiagnostics.failedAttempts.forEach((lookup) =>
90+
this.failedAttempts.push(lookup)
91+
);
92+
}
93+
94+
public recordEndpointContactEvent(location: Location): void {
95+
this.locationEndpointsContacted.set(location.databaseAccountEndpoint, location);
96+
}
97+
98+
public getDiagnostics(): CosmosDiagnostics {
99+
this.recordSessionEnd();
100+
return new CosmosDiagnostics(
101+
v4(),
102+
{
103+
activityId: this.getActivityId(),
104+
requestStartTimeUTCInMs: this.requestStartTimeUTCinMs,
105+
requestEndTimeUTCInMs: this.requestEndTimeUTCinMs,
106+
locationEndpointsContacted: [...this.locationEndpointsContacted.values()],
107+
metadataDiagnostics: {
108+
metadataLookups: [...this.metadataLookups],
109+
},
110+
retryDiagnostics: {
111+
failedAttempts: [...this.failedAttempts],
112+
},
113+
requestPayloadLength: this.requestPayloadLength,
114+
responsePayloadLength: this.responsePayloadLength,
115+
},
116+
this
117+
);
118+
}
119+
120+
private recordSessionEnd() {
121+
this.requestEndTimeUTCinMs = getCurrentTimestampInMs();
122+
}
123+
124+
private getActivityId(): string {
125+
return this.headers[Constants.HttpHeaders.ActivityId] as string;
126+
}
127+
}
128+
129+
/**
130+
* @hidden
131+
* Utility function to get currentTime in UTC milliseconds.
132+
* @returns
133+
*/
134+
export function getCurrentTimestampInMs(): number {
135+
return Date.now();
136+
}

0 commit comments

Comments
 (0)