Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/client-side-metrics/gcp-metrics-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const {
} = require('@opentelemetry/sdk-metrics');
import * as os from 'os';
import * as crypto from 'crypto';
import {MethodName} from './client-side-metrics-attributes';

/**
* Generates a unique client identifier string.
Expand Down Expand Up @@ -251,10 +252,16 @@ export class GCPMetricsHandler implements IMetricsHandler {
status: data.status,
...commonAttributes,
});
otelInstruments.firstResponseLatencies.record(data.firstResponseLatency, {
status: data.status,
...commonAttributes,
});
if (
data.metricsCollectorData.method === MethodName.READ_ROWS ||
data.metricsCollectorData.method === MethodName.READ_ROW
) {
otelInstruments.firstResponseLatencies.record(data.firstResponseLatency, {
status: data.status,
...commonAttributes,
});
}

if (data.applicationLatency) {
otelInstruments.applicationBlockingLatencies.record(
data.applicationLatency,
Expand Down
7 changes: 2 additions & 5 deletions src/client-side-metrics/operation-metrics-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,7 @@ export class OperationMetricsCollector {
}) => {
this.onStatusMetadataReceived(status);
},
)
.on('data', () => {
this.onResponse();
});
);
}

/**
Expand Down Expand Up @@ -310,7 +307,7 @@ export class OperationMetricsCollector {
client_name: `nodejs-bigtable/${version}`,
operationLatency: totalMilliseconds,
retryCount: this.attemptCount - 1,
firstResponseLatency: this.firstResponseLatency ?? undefined,
firstResponseLatency: this.firstResponseLatency ?? 0,
applicationLatency: applicationLatency ?? 0,
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/utils/createReadStreamInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ export function createReadStreamInternal(
gaxOpts,
retryOpts,
});
requestStream.on('data', () => {
// This handler is necessary for recording firstResponseLatencies.
metricsCollector.onResponse();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment that this is for firstResponseLatency?

(This does make me wonder if we can remove this handler after it is called once for performance reasons, to avoid all those extra method calls when we only want the first one. ReadRow's on('data') is the most performance-sensitive part of the library. But maybe that's out of scope here)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I backlogged the suggestion of detaching the handler at https://b.corp.google.com/issues/435417385. I don't want it to be blocking readModifyWriteRow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment

});

activeRequestStream = requestStream!;

Expand Down
30 changes: 24 additions & 6 deletions system-test/client-side-metrics-all-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import {ClientOptions} from 'google-gax';
import {ClientSideMetricsConfigManager} from '../src/client-side-metrics/metrics-config-manager';
import {MetricServiceClient} from '@google-cloud/monitoring';
import {MethodName} from '../src/client-side-metrics/client-side-metrics-attributes';

const SECOND_PROJECT_ID = 'cfdb-sdk-node-tests';

Expand Down Expand Up @@ -66,6 +67,25 @@ function getHandlerFromExporter(Exporter: typeof CloudMonitoringExporter) {
}).GCPMetricsHandler;
}

function checkFirstResponseLatency(requestHandled: OnOperationCompleteData) {
assert(
Object.prototype.hasOwnProperty.call(
requestHandled,
'firstResponseLatency',
),
);
if (
requestHandled.metricsCollectorData.method === MethodName.READ_ROWS ||
requestHandled.metricsCollectorData.method === MethodName.READ_ROW
) {
assert(requestHandled.firstResponseLatency);
assert(requestHandled.firstResponseLatency > 0);
} else {
assert.strictEqual(requestHandled.firstResponseLatency, 0);
}
delete requestHandled.firstResponseLatency;
}

function readRowsAssertionCheck(
projectId: string,
requestsHandled: (OnOperationCompleteData | OnAttemptCompleteData)[] = [],
Expand Down Expand Up @@ -98,11 +118,10 @@ function readRowsAssertionCheck(
const secondRequest = requestsHandled[1] as any;
// We would expect these parameters to be different every time so delete
// them from the comparison after checking they exist.
checkFirstResponseLatency(secondRequest);
assert(secondRequest.operationLatency);
assert(secondRequest.firstResponseLatency);
assert.strictEqual(secondRequest.applicationLatency, 0);
delete secondRequest.operationLatency;
delete secondRequest.firstResponseLatency;
assert(secondRequest.applicationLatency < 10);
delete secondRequest.applicationLatency;
delete secondRequest.metricsCollectorData.appProfileId;
assert.deepStrictEqual(secondRequest, {
Expand Down Expand Up @@ -144,11 +163,10 @@ function readRowsAssertionCheck(
const fourthRequest = requestsHandled[3] as any;
// We would expect these parameters to be different every time so delete
// them from the comparison after checking they exist.
checkFirstResponseLatency(fourthRequest);
assert(fourthRequest.operationLatency);
assert(fourthRequest.firstResponseLatency);
assert.strictEqual(fourthRequest.applicationLatency, 0);
assert(fourthRequest.applicationLatency < 10);
delete fourthRequest.operationLatency;
delete fourthRequest.firstResponseLatency;
delete fourthRequest.applicationLatency;
delete fourthRequest.metricsCollectorData.appProfileId;
assert.deepStrictEqual(fourthRequest, {
Expand Down
Loading