Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 6ed98fa

Browse files
authored
feat: Collect client side metrics for sampleRowKeys calls (#1660)
## Description This PR makes it so that when sampleRowKeys calls are made that metrics will be collected for those calls and available to view in the Google Cloud Monitoring dashboard. ## Impact Now we collect client side metrics for sample row keys so that users can gain insights about what happens when a sampleRowKeys call gets made. ## Testing For all client side metrics tests that exist for ReadRows/MutateRows calls, a test will be added for sample row keys calls.
1 parent ce1e796 commit 6ed98fa

2 files changed

Lines changed: 250 additions & 11 deletions

File tree

src/tabular-api-surface.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -462,16 +462,29 @@ Please use the format 'prezzy' or '${instance.name}/tables/prezzy'.`);
462462
},
463463
objectMode: true,
464464
});
465-
466-
return pumpify.obj([
467-
this.bigtable.request({
468-
client: 'BigtableClient',
469-
method: 'sampleRowKeys',
470-
reqOpts,
471-
gaxOpts: Object.assign({}, gaxOptions),
472-
}),
473-
rowKeysStream,
474-
]);
465+
const metricsCollector =
466+
this.bigtable._metricsConfigManager.createOperation(
467+
MethodName.SAMPLE_ROW_KEYS,
468+
StreamingState.STREAMING,
469+
this,
470+
);
471+
metricsCollector.onOperationStart();
472+
metricsCollector.onAttemptStart();
473+
const requestStream = this.bigtable.request({
474+
client: 'BigtableClient',
475+
method: 'sampleRowKeys',
476+
reqOpts,
477+
gaxOpts: Object.assign({}, gaxOptions),
478+
});
479+
metricsCollector.wrapRequest(requestStream);
480+
const stream = pumpify.obj([requestStream, rowKeysStream]);
481+
stream.on('end', () => {
482+
metricsCollector.onOperationComplete(0);
483+
});
484+
stream.on('error', (err: ServiceError) => {
485+
metricsCollector.onOperationComplete(err.code);
486+
});
487+
return stream;
475488
}
476489
}
477490

system-test/client-side-metrics-all-methods.ts

Lines changed: 227 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ function checkMutateRowsCall(
215215
);
216216
}
217217

218+
function checkSampleRowKeysCall(
219+
projectId: string,
220+
requestsHandled: (OnOperationCompleteData | OnAttemptCompleteData)[] = [],
221+
) {
222+
readRowsAssertionCheck(
223+
projectId,
224+
requestsHandled,
225+
'Bigtable.SampleRowKeys',
226+
'true',
227+
);
228+
}
229+
218230
function checkMutateRowCall(
219231
projectId: string,
220232
requestsHandled: (OnOperationCompleteData | OnAttemptCompleteData)[] = [],
@@ -440,6 +452,86 @@ describe('Bigtable/ClientSideMetricsAllMethods', () => {
440452
);
441453
}
442454

455+
describe('SampleRowKeys', () => {
456+
it('should send the metrics to Google Cloud Monitoring for a SampleRowKeys call', done => {
457+
(async () => {
458+
try {
459+
const bigtable = await mockBigtable(defaultProjectId, done);
460+
for (const instanceId of [instanceId1, instanceId2]) {
461+
await setupBigtableWithInsert(
462+
bigtable,
463+
columnFamilyId,
464+
instanceId,
465+
[tableId1, tableId2],
466+
);
467+
const instance = bigtable.instance(instanceId);
468+
const table = instance.table(tableId1);
469+
await table.sampleRowKeys();
470+
const table2 = instance.table(tableId2);
471+
await table2.sampleRowKeys();
472+
}
473+
} catch (e) {
474+
done(new Error('An error occurred while running the script'));
475+
done(e);
476+
}
477+
})().catch(err => {
478+
throw err;
479+
});
480+
});
481+
it('should send the metrics to Google Cloud Monitoring for a custom endpoint', done => {
482+
(async () => {
483+
try {
484+
const bigtable = await mockBigtable(
485+
defaultProjectId,
486+
done,
487+
'bogus-endpoint',
488+
);
489+
const instance = bigtable.instance(instanceId1);
490+
const table = instance.table(tableId1);
491+
try {
492+
// This call will fail because we are trying to hit a bogus endpoint.
493+
// The idea here is that we just want to record at least one metric
494+
// so that the exporter gets executed.
495+
await table.sampleRowKeys();
496+
} catch (e: unknown) {
497+
// Try blocks just need a catch/finally block.
498+
}
499+
} catch (e) {
500+
done(new Error('An error occurred while running the script'));
501+
done(e);
502+
}
503+
})().catch(err => {
504+
throw err;
505+
});
506+
});
507+
it('should send the metrics to Google Cloud Monitoring for a SampleRowKeys call with a second project', done => {
508+
(async () => {
509+
try {
510+
// This is the second project the test is configured to work with:
511+
const projectId = SECOND_PROJECT_ID;
512+
const bigtable = await mockBigtable(projectId, done);
513+
for (const instanceId of [instanceId1, instanceId2]) {
514+
await setupBigtableWithInsert(
515+
bigtable,
516+
columnFamilyId,
517+
instanceId,
518+
[tableId1, tableId2],
519+
);
520+
const instance = bigtable.instance(instanceId);
521+
const table = instance.table(tableId1);
522+
await table.sampleRowKeys();
523+
const table2 = instance.table(tableId2);
524+
await table2.sampleRowKeys();
525+
}
526+
} catch (e) {
527+
done(new Error('An error occurred while running the script'));
528+
done(e);
529+
}
530+
})().catch(err => {
531+
throw err;
532+
});
533+
});
534+
});
443535
describe('ReadRows', () => {
444536
it('should send the metrics to Google Cloud Monitoring for a ReadRows call', done => {
445537
(async () => {
@@ -825,6 +917,107 @@ describe('Bigtable/ClientSideMetricsAllMethods', () => {
825917
return getFakeBigtable(projectId, getHandlerFromExporter(TestExporter));
826918
}
827919

920+
describe('SampleRowKeys', () => {
921+
it('should send the metrics to Google Cloud Monitoring for a SampleRowKeys call', done => {
922+
let testFinished = false;
923+
/*
924+
We need to create a timeout here because if we don't then mocha shuts down
925+
the test as it is sleeping before the GCPMetricsHandler has a chance to
926+
export the data. When the timeout is finished, if there were no export
927+
errors then the test passes.
928+
*/
929+
setTimeout(() => {
930+
testFinished = true;
931+
done();
932+
}, 120000);
933+
(async () => {
934+
try {
935+
const bigtable1 = await mockBigtable(defaultProjectId, done);
936+
const bigtable2 = await mockBigtable(defaultProjectId, done);
937+
for (const bigtable of [bigtable1, bigtable2]) {
938+
for (const instanceId of [instanceId1, instanceId2]) {
939+
await setupBigtableWithInsert(
940+
bigtable,
941+
columnFamilyId,
942+
instanceId,
943+
[tableId1, tableId2],
944+
);
945+
const instance = bigtable.instance(instanceId);
946+
const table = instance.table(tableId1);
947+
await table.sampleRowKeys();
948+
const table2 = instance.table(tableId2);
949+
await table2.sampleRowKeys();
950+
}
951+
}
952+
} catch (e) {
953+
done(new Error('An error occurred while running the script'));
954+
done(e);
955+
}
956+
})().catch(err => {
957+
throw err;
958+
});
959+
});
960+
it('should send the metrics to Google Cloud Monitoring for a SampleRowKeys call with thirty clients', done => {
961+
/*
962+
We need to create a timeout here because if we don't then mocha shuts down
963+
the test as it is sleeping before the GCPMetricsHandler has a chance to
964+
export the data. When the timeout is finished, if there were no export
965+
errors then the test passes.
966+
*/
967+
const testTimeout = setTimeout(() => {
968+
done(new Error('The test timed out'));
969+
}, 480000);
970+
let testComplete = false;
971+
const numClients = 30;
972+
(async () => {
973+
try {
974+
const bigtableList = [];
975+
const completedSet = new Set();
976+
for (
977+
let bigtableCount = 0;
978+
bigtableCount < numClients;
979+
bigtableCount++
980+
) {
981+
const currentCount = bigtableCount;
982+
const onExportSuccess = () => {
983+
completedSet.add(currentCount);
984+
if (completedSet.size === numClients) {
985+
// If every client has completed the export then pass the test.
986+
clearTimeout(testTimeout);
987+
if (!testComplete) {
988+
testComplete = true;
989+
done();
990+
}
991+
}
992+
};
993+
bigtableList.push(
994+
await mockBigtable(defaultProjectId, done, onExportSuccess),
995+
);
996+
}
997+
for (const bigtable of bigtableList) {
998+
for (const instanceId of [instanceId1, instanceId2]) {
999+
await setupBigtableWithInsert(
1000+
bigtable,
1001+
columnFamilyId,
1002+
instanceId,
1003+
[tableId1, tableId2],
1004+
);
1005+
const instance = bigtable.instance(instanceId);
1006+
const table = instance.table(tableId1);
1007+
await table.sampleRowKeys();
1008+
const table2 = instance.table(tableId2);
1009+
await table2.sampleRowKeys();
1010+
}
1011+
}
1012+
} catch (e) {
1013+
done(e);
1014+
done(new Error('An error occurred while running the script'));
1015+
}
1016+
})().catch(err => {
1017+
throw err;
1018+
});
1019+
});
1020+
});
8281021
describe('ReadRows', () => {
8291022
it('should send the metrics to Google Cloud Monitoring for a ReadRows call', done => {
8301023
let testFinished = false;
@@ -1295,7 +1488,40 @@ describe('Bigtable/ClientSideMetricsAllMethods', () => {
12951488
]);
12961489
return bigtable;
12971490
}
1298-
1491+
describe('SampleRowKeys', () => {
1492+
it('should send the metrics to the metrics handler for a SampleRowKeys call', done => {
1493+
(async () => {
1494+
const bigtable = await mockBigtableWithNoInserts(
1495+
defaultProjectId,
1496+
done,
1497+
checkSampleRowKeysCall,
1498+
);
1499+
const instance = bigtable.instance(instanceId1);
1500+
const table = instance.table(tableId1);
1501+
await table.sampleRowKeys();
1502+
const table2 = instance.table(tableId2);
1503+
await table2.sampleRowKeys();
1504+
})().catch(err => {
1505+
throw err;
1506+
});
1507+
});
1508+
it('should pass the projectId to the metrics handler properly', done => {
1509+
(async () => {
1510+
const bigtable = await mockBigtableWithNoInserts(
1511+
defaultProjectId,
1512+
done,
1513+
checkSampleRowKeysCall,
1514+
);
1515+
const instance = bigtable.instance(instanceId1);
1516+
const table = instance.table(tableId1);
1517+
await table.sampleRowKeys();
1518+
const table2 = instance.table(tableId2);
1519+
await table2.sampleRowKeys();
1520+
})().catch(err => {
1521+
throw err;
1522+
});
1523+
});
1524+
});
12991525
describe('ReadRows', () => {
13001526
it('should send the metrics to the metrics handler for a ReadRows call', done => {
13011527
(async () => {

0 commit comments

Comments
 (0)