Skip to content

Commit 9be3b6a

Browse files
authored
feat: Transfer Manager Metrics (#2305)
* feat: Transfer Manager Metrics * fix: Add missing `gccl-gcs-cmd/` prefix * fix: remove unused method * refactor: update logic for both headers * refactor: use `Symbol.for` in case the file is evaluated multiple times * fix: add missing URL param * fix: if header doesn't exist, add it * test: Add `GCCL_GCS_CMD_KEY` TM tests * feat: Add base `x-goog-api-client` for XML API * chore: dep type change * fix: pass `GCCL_GCS_CMD_KEY` * refactor: remove unused * test: Add `GCCL_GCS_CMD` tests * chore: cleanup
1 parent 7f58f30 commit 9be3b6a

File tree

10 files changed

+323
-70
lines changed

10 files changed

+323
-70
lines changed

src/file.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
ApiError,
6262
Duplexify,
6363
DuplexifyConstructor,
64+
GCCL_GCS_CMD_KEY,
6465
} from './nodejs-common/util';
6566
// eslint-disable-next-line @typescript-eslint/no-var-requires
6667
const duplexify: DuplexifyConstructor = require('duplexify');
@@ -221,6 +222,7 @@ type PublicResumableUploadOptions =
221222
export interface CreateResumableUploadOptions
222223
extends Pick<resumableUpload.UploadConfig, PublicResumableUploadOptions> {
223224
preconditionOpts?: PreconditionOptions;
225+
[GCCL_GCS_CMD_KEY]?: resumableUpload.UploadConfig[typeof GCCL_GCS_CMD_KEY];
224226
}
225227

226228
export type CreateResumableUploadResponse = [string];
@@ -371,6 +373,7 @@ export interface CreateReadStreamOptions {
371373
start?: number;
372374
end?: number;
373375
decompress?: boolean;
376+
[GCCL_GCS_CMD_KEY]?: string;
374377
}
375378

376379
export interface SaveOptions extends CreateWriteStreamOptions {
@@ -1580,12 +1583,16 @@ class File extends ServiceObject<File, FileMetadata> {
15801583
headers.Range = `bytes=${tailRequest ? end : `${start}-${end}`}`;
15811584
}
15821585

1583-
const reqOpts = {
1586+
const reqOpts: DecorateRequestOptions = {
15841587
uri: '',
15851588
headers,
15861589
qs: query,
15871590
};
15881591

1592+
if (options[GCCL_GCS_CMD_KEY]) {
1593+
reqOpts[GCCL_GCS_CMD_KEY] = options[GCCL_GCS_CMD_KEY];
1594+
}
1595+
15891596
this.requestStream(reqOpts)
15901597
.on('error', err => {
15911598
throughStream.destroy(err);
@@ -1738,6 +1745,7 @@ class File extends ServiceObject<File, FileMetadata> {
17381745
userProject: options.userProject || this.userProject,
17391746
retryOptions: retryOptions,
17401747
params: options?.preconditionOpts || this.instancePreconditionOpts,
1748+
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
17411749
},
17421750
callback!
17431751
);
@@ -3907,6 +3915,7 @@ class File extends ServiceObject<File, FileMetadata> {
39073915
params: options?.preconditionOpts || this.instancePreconditionOpts,
39083916
chunkSize: options?.chunkSize,
39093917
highWaterMark: options?.highWaterMark,
3918+
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
39103919
});
39113920

39123921
uploadStream
@@ -3951,6 +3960,7 @@ class File extends ServiceObject<File, FileMetadata> {
39513960
name: this.name,
39523961
},
39533962
uri: uri,
3963+
[GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
39543964
};
39553965

39563966
if (this.generation !== undefined) {

src/nodejs-common/service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {Interceptor} from './service-object';
2121
import {
2222
BodyResponseCallback,
2323
DecorateRequestOptions,
24+
GCCL_GCS_CMD_KEY,
2425
MakeAuthenticatedRequest,
2526
PackageJson,
2627
util,
@@ -253,6 +254,12 @@ export class Service {
253254
} gccl-invocation-id/${uuid.v4()}`,
254255
};
255256

257+
if (reqOpts[GCCL_GCS_CMD_KEY]) {
258+
reqOpts.headers[
259+
'x-goog-api-client'
260+
] += ` gccl-gcs-cmd/${reqOpts[GCCL_GCS_CMD_KEY]}`;
261+
}
262+
256263
if (reqOpts.shouldReturnStream) {
257264
return this.makeAuthenticatedRequest(reqOpts) as {} as r.Request;
258265
} else {

src/nodejs-common/util.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ import {getRuntimeTrackingString} from '../util';
3636

3737
const packageJson = require('../../../package.json');
3838

39+
/**
40+
* A unique symbol for providing a `gccl-gcs-cmd` value
41+
* for the `X-Goog-API-Client` header.
42+
*
43+
* E.g. the `V` in `X-Goog-API-Client: gccl-gcs-cmd/V`
44+
**/
45+
export const GCCL_GCS_CMD_KEY = Symbol.for('GCCL_GCS_CMD');
46+
3947
// eslint-disable-next-line @typescript-eslint/no-var-requires
4048
const duplexify: DuplexifyConstructor = require('duplexify');
4149

@@ -214,7 +222,9 @@ export interface MakeWritableStreamOptions {
214222
request?: r.Options;
215223

216224
makeAuthenticatedRequest(
217-
reqOpts: r.OptionsWithUri,
225+
reqOpts: r.OptionsWithUri & {
226+
[GCCL_GCS_CMD_KEY]?: string;
227+
},
218228
fnobj: {
219229
onAuthenticated(
220230
err: Error | null,
@@ -233,6 +243,7 @@ export interface DecorateRequestOptions extends r.CoreOptions {
233243
interceptors_?: Interceptor[];
234244
shouldReturnStream?: boolean;
235245
projectId?: string;
246+
[GCCL_GCS_CMD_KEY]?: string;
236247
}
237248

238249
export interface ParsedHttpResponseBody {
@@ -530,7 +541,9 @@ export class Util {
530541
body: writeStream,
531542
},
532543
],
533-
} as {} as r.OptionsWithUri;
544+
} as {} as r.OptionsWithUri & {
545+
[GCCL_GCS_CMD_KEY]?: string;
546+
};
534547

535548
options.makeAuthenticatedRequest(reqOpts, {
536549
onAuthenticated(err, authenticatedReqOpts) {
@@ -539,7 +552,9 @@ export class Util {
539552
return;
540553
}
541554

542-
requestDefaults.headers = util._getDefaultHeaders();
555+
requestDefaults.headers = util._getDefaultHeaders(
556+
reqOpts[GCCL_GCS_CMD_KEY]
557+
);
543558
const request = teenyRequest.defaults(requestDefaults);
544559
request(authenticatedReqOpts!, (err, resp, body) => {
545560
util.handleResp(err, resp, body, (err, data) => {
@@ -863,7 +878,9 @@ export class Util {
863878
maxRetryValue = config.retryOptions.maxRetries;
864879
}
865880

866-
requestDefaults.headers = this._getDefaultHeaders();
881+
requestDefaults.headers = this._getDefaultHeaders(
882+
reqOpts[GCCL_GCS_CMD_KEY]
883+
);
867884
const options = {
868885
request: teenyRequest.defaults(requestDefaults),
869886
retries: autoRetryValue !== false ? maxRetryValue : 0,
@@ -1014,13 +1031,19 @@ export class Util {
10141031
: [optionsOrCallback as T, cb as C];
10151032
}
10161033

1017-
_getDefaultHeaders() {
1018-
return {
1034+
_getDefaultHeaders(gcclGcsCmd?: string) {
1035+
const headers = {
10191036
'User-Agent': util.getUserAgentFromPackageJson(packageJson),
10201037
'x-goog-api-client': `${getRuntimeTrackingString()} gccl/${
10211038
packageJson.version
10221039
} gccl-invocation-id/${uuid.v4()}`,
10231040
};
1041+
1042+
if (gcclGcsCmd) {
1043+
headers['x-goog-api-client'] += ` gccl-gcs-cmd/${gcclGcsCmd}`;
1044+
}
1045+
1046+
return headers;
10241047
}
10251048
}
10261049

src/resumable-upload.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import retry = require('async-retry');
2727
import {RetryOptions, PreconditionOptions} from './storage';
2828
import * as uuid from 'uuid';
2929
import {getRuntimeTrackingString} from './util';
30+
import {GCCL_GCS_CMD_KEY} from './nodejs-common/util';
3031

3132
const NOT_FOUND_STATUS_CODE = 404;
3233
const RESUMABLE_INCOMPLETE_STATUS_CODE = 308;
@@ -193,6 +194,8 @@ export interface UploadConfig extends Pick<WritableOptions, 'highWaterMark'> {
193194
* Configuration options for retrying retryable errors.
194195
*/
195196
retryOptions: RetryOptions;
197+
198+
[GCCL_GCS_CMD_KEY]?: string;
196199
}
197200

198201
export interface ConfigMetadata {
@@ -274,6 +277,7 @@ export class Upload extends Writable {
274277
private localWriteCache: Buffer[] = [];
275278
private localWriteCacheByteLength = 0;
276279
private upstreamEnded = false;
280+
#gcclGcsCmd?: string;
277281

278282
constructor(cfg: UploadConfig) {
279283
super(cfg);
@@ -347,6 +351,8 @@ export class Upload extends Writable {
347351
: NaN;
348352
this.contentLength = isNaN(contentLength) ? '*' : contentLength;
349353

354+
this.#gcclGcsCmd = cfg[GCCL_GCS_CMD_KEY];
355+
350356
this.once('writing', () => {
351357
if (this.uri) {
352358
this.continueUploading();
@@ -585,6 +591,14 @@ export class Upload extends Writable {
585591
delete metadata.contentType;
586592
}
587593

594+
let googAPIClient = `${getRuntimeTrackingString()} gccl/${
595+
packageJson.version
596+
} gccl-invocation-id/${this.currentInvocationId.uri}`;
597+
598+
if (this.#gcclGcsCmd) {
599+
googAPIClient += ` gccl-gcs-cmd/${this.#gcclGcsCmd}`;
600+
}
601+
588602
// Check if headers already exist before creating new ones
589603
const reqOpts: GaxiosOptions = {
590604
method: 'POST',
@@ -598,9 +612,7 @@ export class Upload extends Writable {
598612
),
599613
data: metadata,
600614
headers: {
601-
'x-goog-api-client': `${getRuntimeTrackingString()} gccl/${
602-
packageJson.version
603-
} gccl-invocation-id/${this.currentInvocationId.uri}`,
615+
'x-goog-api-client': googAPIClient,
604616
...headers,
605617
},
606618
};
@@ -766,10 +778,16 @@ export class Upload extends Writable {
766778
},
767779
});
768780

781+
let googAPIClient = `${getRuntimeTrackingString()} gccl/${
782+
packageJson.version
783+
} gccl-invocation-id/${this.currentInvocationId.chunk}`;
784+
785+
if (this.#gcclGcsCmd) {
786+
googAPIClient += ` gccl-gcs-cmd/${this.#gcclGcsCmd}`;
787+
}
788+
769789
const headers: GaxiosOptions['headers'] = {
770-
'x-goog-api-client': `${getRuntimeTrackingString()} gccl/${
771-
packageJson.version
772-
} gccl-invocation-id/${this.currentInvocationId.chunk}`,
790+
'x-goog-api-client': googAPIClient,
773791
};
774792

775793
// If using multiple chunk upload, set appropriate header
@@ -904,15 +922,21 @@ export class Upload extends Writable {
904922
}
905923

906924
private async getAndSetOffset() {
925+
let googAPIClient = `${getRuntimeTrackingString()} gccl/${
926+
packageJson.version
927+
} gccl-invocation-id/${this.currentInvocationId.offset}`;
928+
929+
if (this.#gcclGcsCmd) {
930+
googAPIClient += ` gccl-gcs-cmd/${this.#gcclGcsCmd}`;
931+
}
932+
907933
const opts: GaxiosOptions = {
908934
method: 'PUT',
909935
url: this.uri!,
910936
headers: {
911937
'Content-Length': 0,
912938
'Content-Range': 'bytes */*',
913-
'x-goog-api-client': `${getRuntimeTrackingString()} gccl/${
914-
packageJson.version
915-
} gccl-invocation-id/${this.currentInvocationId.offset}`,
939+
'x-goog-api-client': googAPIClient,
916940
},
917941
};
918942
try {

0 commit comments

Comments
 (0)