Skip to content

Commit cbfdd78

Browse files
committed
[protocol, et al] Extract gRPC ClientCallMetrics into protocol
1 parent 969cb86 commit cbfdd78

File tree

13 files changed

+127
-141
lines changed

13 files changed

+127
-141
lines changed

components/content-service-api/typescript/BUILD.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
packages:
22
- name: lib
33
type: yarn
4+
deps:
5+
- components/gitpod-protocol:lib
46
srcs:
57
- "src/*.ts"
68
- "src/*.js"

components/content-service-api/typescript/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"lib"
1212
],
1313
"dependencies": {
14+
"@gitpod/gitpod-protocol": "0.1.5",
1415
"@grpc/grpc-js": "^1.3.7",
1516
"google-protobuf": "^3.19.1",
1617
"inversify": "^5.0.1",

components/content-service-api/typescript/src/client-call-metrics.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.

components/content-service-api/typescript/src/sugar.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { inject, injectable, interfaces, optional } from "inversify";
88
import * as grpc from "@grpc/grpc-js";
9-
import { createClientCallMetricsInterceptor, IClientCallMetrics } from "./client-call-metrics";
9+
import { createClientCallMetricsInterceptor, IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/util/grpc";
1010
import { IDEPluginServiceClient } from "./ideplugin_grpc_pb";
1111
import { ContentServiceClient } from "./content_grpc_pb";
1212
import { BlobServiceClient } from "./blobs_grpc_pb";
@@ -16,7 +16,10 @@ import { HeadlessLogServiceClient } from "./headless-log_grpc_pb";
1616
export const ContentServiceClientConfig = Symbol("ContentServiceClientConfig");
1717
export const ContentServiceClientCallMetrics = Symbol("ContentServiceClientCallMetrics");
1818

19-
export const contentServiceBinder = (config: (ctx: interfaces.Context) => ContentServiceClientConfig, clientCallMetrics?: IClientCallMetrics): interfaces.ContainerModuleCallBack => {
19+
export const contentServiceBinder = (
20+
config: (ctx: interfaces.Context) => ContentServiceClientConfig,
21+
clientCallMetrics?: IClientCallMetrics,
22+
): interfaces.ContainerModuleCallBack => {
2023
return (bind, unbind, isBound, rebind) => {
2124
bind(ContentServiceClientConfig).toDynamicValue(config).inSingletonScope();
2225
if (clientCallMetrics) {
@@ -50,7 +53,8 @@ export interface ContentServiceClientProvider<T> {
5053
abstract class CachingClientProvider<T> implements ContentServiceClientProvider<T> {
5154
@inject(ContentServiceClientConfig) protected readonly clientConfig: ContentServiceClientConfig;
5255

53-
@inject(ContentServiceClientCallMetrics) @optional()
56+
@inject(ContentServiceClientCallMetrics)
57+
@optional()
5458
protected readonly clientCallMetrics: IClientCallMetrics;
5559

5660
protected readonly interceptors: grpc.Interceptor[] = [];
@@ -59,9 +63,7 @@ abstract class CachingClientProvider<T> implements ContentServiceClientProvider<
5963
// Thus it makes sense to cache them rather than create a new connection for each request.
6064
protected client: Client<T> | undefined;
6165

62-
constructor(
63-
protected readonly createClient: (config: ContentServiceClientConfig) => Client<T>,
64-
) {
66+
constructor(protected readonly createClient: (config: ContentServiceClientConfig) => Client<T>) {
6567
if (this.clientCallMetrics) {
6668
this.interceptors.push(createClientCallMetricsInterceptor(this.clientCallMetrics));
6769
}
@@ -89,8 +91,8 @@ abstract class CachingClientProvider<T> implements ContentServiceClientProvider<
8991
options: {
9092
...(config.options || {}),
9193
interceptors: [...(config.options?.interceptors || []), ...this.interceptors],
92-
}
93-
}
94+
},
95+
};
9496
}
9597
return config;
9698
}
@@ -101,7 +103,7 @@ export class CachingContentServiceClientProvider extends CachingClientProvider<C
101103
constructor() {
102104
super((config) => {
103105
return new ContentServiceClient(config.address, config.credentials, config.options);
104-
})
106+
});
105107
}
106108
}
107109

@@ -110,7 +112,7 @@ export class CachingBlobServiceClientProvider extends CachingClientProvider<Blob
110112
constructor() {
111113
super((config) => {
112114
return new BlobServiceClient(config.address, config.credentials, config.options);
113-
})
115+
});
114116
}
115117
}
116118

@@ -119,7 +121,7 @@ export class CachingWorkspaceServiceClientProvider extends CachingClientProvider
119121
constructor() {
120122
super((config) => {
121123
return new WorkspaceServiceClient(config.address, config.credentials, config.options);
122-
})
124+
});
123125
}
124126
}
125127

@@ -128,7 +130,7 @@ export class CachingIDEPluginClientProvider extends CachingClientProvider<IDEPlu
128130
constructor() {
129131
super((config) => {
130132
return new IDEPluginServiceClient(config.address, config.credentials, config.options);
131-
})
133+
});
132134
}
133135
}
134136

@@ -137,11 +139,15 @@ export class CachingHeadlessLogServiceClientProvider extends CachingClientProvid
137139
constructor() {
138140
super((config) => {
139141
return new HeadlessLogServiceClient(config.address, config.credentials, config.options);
140-
})
142+
});
141143
}
142144
}
143145

144146
function isConnectionAlive(client: grpc.Client) {
145147
const cs = client.getChannel().getConnectivityState(false);
146-
return cs == grpc.connectivityState.CONNECTING || cs == grpc.connectivityState.IDLE || cs == grpc.connectivityState.READY;
148+
return (
149+
cs == grpc.connectivityState.CONNECTING ||
150+
cs == grpc.connectivityState.IDLE ||
151+
cs == grpc.connectivityState.READY
152+
);
147153
}

components/gitpod-protocol/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@types/chai-subset": "^1.3.3",
1515
"@types/cookie": "^0.4.1",
1616
"@types/express": "^4.17.13",
17+
"@grpc/grpc-js": "^1.3.7",
1718
"@types/jaeger-client": "^3.18.3",
1819
"@types/js-yaml": "^3.10.1",
1920
"@types/mocha": "^5.2.7",

components/gitpod-protocol/src/messaging/client-call-metrics.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,7 @@
66

77
import { injectable } from "inversify";
88
import * as prometheusClient from "prom-client";
9-
10-
type GrpcMethodType = "unary" | "client_stream" | "server_stream" | "bidi_stream";
11-
export interface IGrpcCallMetricsLabels {
12-
service: string;
13-
method: string;
14-
type: GrpcMethodType;
15-
}
16-
17-
export interface IGrpcCallMetricsLabelsWithCode extends IGrpcCallMetricsLabels {
18-
code: string;
19-
}
20-
21-
export const IClientCallMetrics = Symbol("IClientCallMetrics");
22-
23-
export interface IClientCallMetrics {
24-
started(labels: IGrpcCallMetricsLabels): void;
25-
sent(labels: IGrpcCallMetricsLabels): void;
26-
received(labels: IGrpcCallMetricsLabels): void;
27-
handled(labels: IGrpcCallMetricsLabelsWithCode): void;
28-
}
9+
import { IClientCallMetrics, IGrpcCallMetricsLabels, IGrpcCallMetricsLabelsWithCode } from "../util/grpc";
2910

3011
@injectable()
3112
export class PrometheusClientCallMetrics implements IClientCallMetrics {

components/gitpod-protocol/src/util/grpc.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7+
import * as grpc from "@grpc/grpc-js";
8+
import { Status } from "@grpc/grpc-js/build/src/constants";
9+
710
export const defaultGRPCOptions = {
811
"grpc.keepalive_timeout_ms": 10000,
912
"grpc.keepalive_time_ms": 60000,
@@ -13,3 +16,89 @@ export const defaultGRPCOptions = {
1316
"grpc.max_reconnect_backoff_ms": 5000,
1417
"grpc.max_receive_message_length": 1024 * 1024 * 16,
1518
};
19+
20+
export type GrpcMethodType = "unary" | "client_stream" | "server_stream" | "bidi_stream";
21+
22+
export interface IGrpcCallMetricsLabels {
23+
service: string;
24+
method: string;
25+
type: GrpcMethodType;
26+
}
27+
28+
export interface IGrpcCallMetricsLabelsWithCode extends IGrpcCallMetricsLabels {
29+
code: string;
30+
}
31+
32+
export const IClientCallMetrics = Symbol("IClientCallMetrics");
33+
34+
export interface IClientCallMetrics {
35+
started(labels: IGrpcCallMetricsLabels): void;
36+
sent(labels: IGrpcCallMetricsLabels): void;
37+
received(labels: IGrpcCallMetricsLabels): void;
38+
handled(labels: IGrpcCallMetricsLabelsWithCode): void;
39+
}
40+
41+
export function getGrpcMethodType(requestStream: boolean, responseStream: boolean): GrpcMethodType {
42+
if (requestStream) {
43+
if (responseStream) {
44+
return "bidi_stream";
45+
} else {
46+
return "client_stream";
47+
}
48+
} else {
49+
if (responseStream) {
50+
return "server_stream";
51+
} else {
52+
return "unary";
53+
}
54+
}
55+
}
56+
57+
export function createClientCallMetricsInterceptor(metrics: IClientCallMetrics): grpc.Interceptor {
58+
return (options, nextCall): grpc.InterceptingCall => {
59+
const methodDef = options.method_definition;
60+
const method = methodDef.path.substring(methodDef.path.lastIndexOf("/") + 1);
61+
const service = methodDef.path.substring(1, methodDef.path.length - method.length - 1);
62+
const labels = {
63+
service,
64+
method,
65+
type: getGrpcMethodType(options.method_definition.requestStream, options.method_definition.responseStream),
66+
};
67+
const requester = new grpc.RequesterBuilder()
68+
.withStart((metadata, listener, next) => {
69+
const newListener = new grpc.ListenerBuilder()
70+
.withOnReceiveStatus((status, next) => {
71+
try {
72+
metrics.handled({
73+
...labels,
74+
code: Status[status.code],
75+
});
76+
} finally {
77+
next(status);
78+
}
79+
})
80+
.withOnReceiveMessage((message, next) => {
81+
try {
82+
metrics.received(labels);
83+
} finally {
84+
next(message);
85+
}
86+
})
87+
.build();
88+
try {
89+
metrics.started(labels);
90+
} finally {
91+
next(metadata, newListener);
92+
}
93+
})
94+
.withSendMessage((message, next) => {
95+
try {
96+
metrics.sent(labels);
97+
} finally {
98+
next(message);
99+
}
100+
})
101+
.build();
102+
return new grpc.InterceptingCall(nextCall(options), requester);
103+
};
104+
}

components/image-builder-api/typescript/src/sugar.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import { ImageBuilderClient } from "./imgbuilder_grpc_pb";
88
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
99
import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred";
1010
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
11-
import {
12-
createClientCallMetricsInterceptor,
13-
IClientCallMetrics,
14-
} from "@gitpod/content-service/lib/client-call-metrics";
11+
import { createClientCallMetricsInterceptor, IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/util/grpc";
1512
import * as opentracing from "opentracing";
1613
import { Metadata } from "@grpc/grpc-js";
1714
import {

components/server/src/container-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ import { Config, ConfigFile } from "./config";
9191
import { defaultGRPCOptions } from "@gitpod/gitpod-protocol/lib/util/grpc";
9292
import { IDEConfigService } from "./ide-config";
9393
import { PrometheusClientCallMetrics } from "@gitpod/gitpod-protocol/lib/messaging/client-call-metrics";
94-
import { IClientCallMetrics } from "@gitpod/content-service/lib/client-call-metrics";
94+
import { IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/util/grpc";
9595
import { DebugApp } from "@gitpod/gitpod-protocol/lib/util/debug-app";
9696
import { LocalMessageBroker, LocalRabbitMQBackedMessageBroker } from "./messaging/local-message-broker";
9797
import { contentServiceBinder } from "@gitpod/content-service/lib/sugar";

components/server/src/workspace/workspace-cluster-imagebuilder-client-provider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
*/
66

77
import { Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol";
8-
import { IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/messaging/client-call-metrics";
9-
import { defaultGRPCOptions } from "@gitpod/gitpod-protocol/lib/util/grpc";
8+
import { defaultGRPCOptions, IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/util/grpc";
109
import {
1110
ImageBuilderClient,
1211
ImageBuilderClientCallMetrics,

0 commit comments

Comments
 (0)