Skip to content

Commit a2c33ff

Browse files
committed
Address review comments
1 parent bdb4f5c commit a2c33ff

File tree

6 files changed

+81
-85
lines changed

6 files changed

+81
-85
lines changed

src/metrics/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ The custom metric exporter, as defined in [spanner-metrics-exporter.ts](./spanne
44
## Filtering Criteria
55
The exporter filters metrics based on the following conditions, utilizing values defined in [constants.ts](./constants.ts):
66

7-
* Metrics with a scope set to `gax-nodejs`.
7+
* Metrics with a scope set to `spanner-nodejs`.
88
* Metrics with one of the following predefined names:
99
* `attempt_latencies`
1010
* `attempt_count`
1111
* `operation_latencies`
1212
* `operation_count`
1313
* `gfe_latency`
14-
* `gfe_missing_header_count`
14+
* `gfe_connectivity_error_count`
1515

1616
## Service Endpoint
1717
The exporter sends metrics to the Google Cloud Monitoring [service endpoint](https://cloud.google.com/python/docs/reference/monitoring/latest/google.cloud.monitoring_v3.services.metric_service.MetricServiceClient#google_cloud_monitoring_v3_services_metric_service_MetricServiceClient_create_service_time_series), distinct from the regular client endpoint. This service endpoint operates under a different quota limit than the user endpoint and features an additional server-side filter that only permits a predefined set of metrics to pass through.

src/metrics/external-types.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import {GoogleAuthOptions} from 'google-auth-library';
15+
import {GoogleAuth} from 'google-auth-library';
1616

1717
export interface ExporterOptions {
1818
/**
1919
* Optional authentication options for Google services.
2020
*/
21-
authOptions?: GoogleAuthOptions;
22-
/**
23-
* Prefix prepended to OpenTelemetry metric names when writing to Cloud Monitoring. See
24-
* https://cloud.google.com/monitoring/custom-metrics#identifier for more details.
25-
*
26-
* Optional, default is `workload.googleapis.com`.
27-
*/
28-
prefix?: string;
21+
auth: GoogleAuth;
2922
/**
3023
* Add a custom user agent and version strings to all monitoring exports
3124
*/

src/metrics/spanner-metrics-exporter.ts

+17-19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {GoogleAuth, JWT} from 'google-auth-library';
2323
import {monitoring_v3} from 'googleapis';
2424
import {transformResourceMetricToTimeSeriesArray} from './transform';
2525
import {version} from '../../package.json';
26+
import {status} from '@grpc/grpc-js';
2627

2728
// Stackdriver Monitoring v3 only accepts up to 200 TimeSeries per
2829
// CreateTimeSeries call.
@@ -54,14 +55,8 @@ export class CloudMonitoringMetricsExporter implements PushMetricExporter {
5455

5556
private _monitoring: monitoring_v3.Monitoring;
5657

57-
constructor({authOptions, userAgent, apiEndpoint}: ExporterOptions = {}) {
58-
this._auth = new GoogleAuth({
59-
credentials: authOptions?.credentials,
60-
keyFile: authOptions?.keyFile,
61-
keyFilename: authOptions?.keyFilename,
62-
projectId: authOptions?.projectId,
63-
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
64-
});
58+
constructor({auth, userAgent, apiEndpoint}: ExporterOptions) {
59+
this._auth = auth;
6560

6661
this._monitoring = new monitoring_v3.Monitoring({
6762
rootUrl: `https://${apiEndpoint || DEFAULT_API_ENDPOINT}`,
@@ -125,18 +120,21 @@ export class CloudMonitoringMetricsExporter implements PushMetricExporter {
125120
};
126121
await Promise.all(
127122
this._partitionList(timeSeriesList, MAX_BATCH_EXPORT_SIZE).map(
128-
async batchedTimeSeries => {
129-
try {
130-
await this._sendTimeSeries(batchedTimeSeries);
131-
} catch (e) {
132-
const err = asError(e);
133-
err.message = `Send TimeSeries failed: ${err.message}`;
134-
failure = {sendFailed: true, error: err};
135-
console.error(`ERROR: ${err.message}`);
136-
}
137-
}
123+
async batchedTimeSeries => this._sendTimeSeries(batchedTimeSeries)
138124
)
139-
);
125+
).catch(e => {
126+
const error = e as {code: number};
127+
if (error.code === status.PERMISSION_DENIED) {
128+
console.warn(
129+
`Need monitoring metric writer permission on project ${this._projectId}. Follow https://cloud.google.com/spanner/docs/view-manage-client-side-metrics#access-client-side-metrics to set up permissions`
130+
);
131+
}
132+
const err = asError(e);
133+
err.message = `Send TimeSeries failed: ${err.message}`;
134+
failure = {sendFailed: true, error: err};
135+
console.error(`ERROR: ${err.message}`);
136+
});
137+
140138
return failure.sendFailed
141139
? {
142140
code: ExportResultCode.FAILED,

src/metrics/transform.ts

+24-22
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
} from './constants';
3636

3737
/** Transforms a OpenTelemetry instrument type to a GCM MetricKind. */
38-
function transformMetricKind(metric: MetricData): MetricKind {
38+
function _transformMetricKind(metric: MetricData): MetricKind {
3939
switch (metric.dataPointType) {
4040
case DataPointType.SUM:
4141
return metric.isMonotonic ? MetricKind.CUMULATIVE : MetricKind.GAUGE;
@@ -52,15 +52,17 @@ function transformMetricKind(metric: MetricData): MetricKind {
5252
}
5353

5454
/** Transforms resource to Google Cloud Monitoring monitored resource */
55-
function transformResource(labels: {[key: string]: string}): MonitoredResource {
55+
function _transformResource(labels: {
56+
[key: string]: string;
57+
}): MonitoredResource {
5658
return {
5759
type: SPANNER_RESOURCE_TYPE,
5860
labels: labels,
5961
} as MonitoredResource;
6062
}
6163

6264
/** Transforms a OpenTelemetry ValueType to a GCM ValueType. */
63-
function transformValueType(metric: MetricData): ValueType {
65+
function _transformValueType(metric: MetricData): ValueType {
6466
const {
6567
dataPointType,
6668
descriptor: {name},
@@ -88,7 +90,7 @@ export function transformResourceMetricToTimeSeriesArray({
8890

8991
return (
9092
scopeMetrics
91-
// Only keep those whose scope.name matches 'gax-nodejs'.
93+
// Only keep those whose scope.name matches 'spanner-nodejs'.
9294
.filter(({scope: {name}}) => name === SPANNER_METER_NAME)
9395
// Takes each metric array and flattens it into one array
9496
.flatMap(({metrics}) =>
@@ -98,15 +100,15 @@ export function transformResourceMetricToTimeSeriesArray({
98100
// Flatmap the data points in each metric to create a TimeSeries for each point
99101
.flatMap(metric =>
100102
metric.dataPoints.flatMap(dataPoint =>
101-
createTimeSeries(metric, dataPoint)
103+
_createTimeSeries(metric, dataPoint)
102104
)
103105
)
104106
);
105107
}
106108
/**
107109
* Creates a GCM TimeSeries.
108110
*/
109-
function createTimeSeries<T>(
111+
function _createTimeSeries<T>(
110112
metric: MetricData,
111113
dataPoint: DataPoint<T>
112114
): monitoring_v3.Schema$TimeSeries {
@@ -120,27 +122,27 @@ function createTimeSeries<T>(
120122

121123
return {
122124
metric: transformedMetric,
123-
resource: transformResource(monitoredResourceLabels),
124-
metricKind: transformMetricKind(metric),
125-
valueType: transformValueType(metric),
126-
points: [transformPoint(metric, dataPoint)],
125+
resource: _transformResource(monitoredResourceLabels),
126+
metricKind: _transformMetricKind(metric),
127+
valueType: _transformValueType(metric),
128+
points: [_transformPoint(metric, dataPoint)],
127129
unit: metric.descriptor.unit,
128130
};
129131
}
130132

131133
/**
132134
* Transform timeseries's point, so that metric can be uploaded to GCM.
133135
*/
134-
function transformPoint<T>(
136+
function _transformPoint<T>(
135137
metric: MetricData,
136138
dataPoint: DataPoint<T>
137139
): monitoring_v3.Schema$Point {
138140
switch (metric.dataPointType) {
139141
case DataPointType.SUM:
140142
case DataPointType.GAUGE:
141143
return {
142-
value: transformNumberValue(
143-
transformValueType(metric),
144+
value: _transformNumberValue(
145+
_transformValueType(metric),
144146
dataPoint.value as number
145147
),
146148
interval: {
@@ -155,15 +157,15 @@ function transformPoint<T>(
155157
};
156158
case DataPointType.HISTOGRAM:
157159
return {
158-
value: transformHistogramValue(dataPoint.value as Histogram),
160+
value: _transformHistogramValue(dataPoint.value as Histogram),
159161
interval: {
160162
startTime: new PreciseDate(dataPoint.startTime).toISOString(),
161163
endTime: new PreciseDate(dataPoint.endTime).toISOString(),
162164
},
163165
};
164166
case DataPointType.EXPONENTIAL_HISTOGRAM:
165167
return {
166-
value: transformExponentialHistogramValue(
168+
value: _transformExponentialHistogramValue(
167169
dataPoint.value as ExponentialHistogram
168170
),
169171
interval: {
@@ -215,7 +217,7 @@ function _normalizeLabelKey(key: string): string {
215217
}
216218

217219
/** Transforms a OpenTelemetry Point's value to a GCM Point value. */
218-
function transformNumberValue(
220+
function _transformNumberValue(
219221
valueType: ValueType,
220222
value: number
221223
): monitoring_v3.Schema$TypedValue {
@@ -227,7 +229,7 @@ function transformNumberValue(
227229
throw Error(`unsupported value type: ${valueType}`);
228230
}
229231

230-
function transformHistogramValue(
232+
function _transformHistogramValue(
231233
value: Histogram
232234
): monitoring_v3.Schema$TypedValue {
233235
return {
@@ -243,7 +245,7 @@ function transformHistogramValue(
243245
};
244246
}
245247

246-
function transformExponentialHistogramValue(
248+
function _transformExponentialHistogramValue(
247249
value: ExponentialHistogram
248250
): monitoring_v3.Schema$TypedValue {
249251
// Adapated from reference impl in Go which has more explanatory comments
@@ -297,10 +299,10 @@ function exhaust(switchValue: never) {
297299

298300
export const _TEST_ONLY = {
299301
_normalizeLabelKey,
300-
transformMetricKind,
302+
_transformMetricKind,
301303
_extractLabels,
302-
transformResource,
303-
transformPoint,
304-
transformValueType,
304+
_transformResource,
305+
_transformPoint,
306+
_transformValueType,
305307
transformResourceMetricToTimeSeriesArray,
306308
};

test/metrics/spanner-metrics-exporter.test.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import * as assert from 'assert';
1616
import * as sinon from 'sinon';
1717
import {MeterProvider, MetricReader} from '@opentelemetry/sdk-metrics';
18+
import {GoogleAuth} from 'google-auth-library';
1819
import {CloudMonitoringMetricsExporter} from '../../src/metrics/spanner-metrics-exporter';
1920
import {
2021
SPANNER_METER_NAME,
@@ -32,23 +33,24 @@ const PROJECT_ID = 'test-project';
3233
const INSTANCE_ID = 'test_instance';
3334
const DATABASE_ID = 'test_db';
3435

36+
const auth = new GoogleAuth();
37+
3538
// Ensure custom exporter is valid
3639
describe('CustomExporter', () => {
3740
it('should construct an exporter', () => {
38-
const exporter = new CloudMonitoringMetricsExporter();
41+
const exporter = new CloudMonitoringMetricsExporter({auth});
3942
assert.ok(typeof exporter.export === 'function');
4043
assert.ok(typeof exporter.shutdown === 'function');
4144
});
4245

4346
it('should construct an exporter with credentials', () => {
44-
const exporter = new CloudMonitoringMetricsExporter({
45-
authOptions: {
46-
credentials: {
47-
client_email: 'noreply@fake.example.com',
48-
private_key: 'this is a key',
49-
},
47+
const auth = new GoogleAuth({
48+
credentials: {
49+
client_email: 'fake',
50+
private_key: '',
5051
},
5152
});
53+
const exporter = new CloudMonitoringMetricsExporter({auth});
5254

5355
assert(exporter);
5456
return (exporter['_projectId'] as Promise<string>).then(id => {
@@ -57,12 +59,12 @@ describe('CustomExporter', () => {
5759
});
5860

5961
it('should be able to shutdown', async () => {
60-
const exporter = new CloudMonitoringMetricsExporter();
62+
const exporter = new CloudMonitoringMetricsExporter({auth});
6163
await assert.doesNotReject(exporter.shutdown());
6264
});
6365

6466
it('should be able to force flush', async () => {
65-
const exporter = new CloudMonitoringMetricsExporter();
67+
const exporter = new CloudMonitoringMetricsExporter({auth});
6668
await assert.doesNotReject(exporter.forceFlush());
6769
});
6870
});
@@ -102,8 +104,6 @@ describe('Export', () => {
102104
database: DATABASE_ID,
103105
method: 'test_method',
104106
status: 'test_status',
105-
directpath_enabled: 'true',
106-
directpath_used: 'false',
107107
other: 'ignored',
108108
};
109109

@@ -140,7 +140,7 @@ describe('Export', () => {
140140
unit: 'ms',
141141
});
142142

143-
exporter = new CloudMonitoringMetricsExporter();
143+
exporter = new CloudMonitoringMetricsExporter({auth});
144144
});
145145

146146
it('should export GCM metrics', async () => {

0 commit comments

Comments
 (0)