Skip to content

Commit c4c6554

Browse files
iQQBotroboquat
authored andcommitted
add nice grpc metrice handler
1 parent 481267b commit c4c6554

File tree

3 files changed

+105
-6
lines changed

3 files changed

+105
-6
lines changed

components/gitpod-protocol/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010
"src"
1111
],
1212
"devDependencies": {
13+
"@grpc/grpc-js": "^1.3.7",
1314
"@types/analytics-node": "^3.1.9",
1415
"@types/chai-subset": "^1.3.3",
1516
"@types/cookie": "^0.4.1",
1617
"@types/express": "^4.17.13",
17-
"@grpc/grpc-js": "^1.3.7",
18+
"@types/google-protobuf": "^3.15.5",
1819
"@types/jaeger-client": "^3.18.3",
1920
"@types/js-yaml": "^3.10.1",
2021
"@types/mocha": "^5.2.7",
2122
"@types/node": "^16.11.6",
2223
"@types/random-number-csprng": "^1.0.0",
2324
"@types/uuid": "^8.3.1",
2425
"@types/ws": "^5.1.2",
25-
"@types/google-protobuf": "^3.15.5",
2626
"chai": "^4.3.4",
2727
"chai-subset": "^1.6.0",
2828
"mocha": "^5.0.0",
@@ -42,14 +42,17 @@
4242
},
4343
"dependencies": {
4444
"@types/react": "17.0.32",
45+
"abort-controller-x": "^0.4.0",
4546
"ajv": "^6.5.4",
4647
"analytics-node": "^6.0.0",
4748
"configcat-node": "^8.0.0",
4849
"cookie": "^0.4.2",
4950
"express": "^4.17.3",
51+
"google-protobuf": "^3.19.1",
5052
"inversify": "^5.1.1",
5153
"jaeger-client": "^3.18.1",
5254
"js-yaml": "^3.10.0",
55+
"nice-grpc-common": "^2.0.0",
5356
"opentracing": "^0.14.5",
5457
"prom-client": "^13.2.0",
5558
"random-number-csprng": "^1.0.2",
@@ -63,7 +66,6 @@
6366
"vscode-languageserver-types": "3.17.0",
6467
"vscode-uri": "^3.0.3",
6568
"vscode-ws-jsonrpc": "^0.2.0",
66-
"ws": "^7.4.6",
67-
"google-protobuf": "^3.19.1"
69+
"ws": "^7.4.6"
6870
}
6971
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { isAbortError } from "abort-controller-x";
8+
import {
9+
CallOptions,
10+
ClientError,
11+
ClientMiddleware,
12+
ClientMiddlewareCall,
13+
Status,
14+
MethodDescriptor,
15+
} from "nice-grpc-common";
16+
import { GrpcMethodType, IClientCallMetrics } from "./grpc";
17+
18+
function getLabels(method: MethodDescriptor) {
19+
const callType = method.requestStream
20+
? method.responseStream
21+
? "bidi_stream"
22+
: "client_stream"
23+
: method.responseStream
24+
? "server_stream"
25+
: "unary";
26+
const { path } = method;
27+
const [serviceName, methodName] = path.split("/").slice(1);
28+
29+
return {
30+
type: callType as GrpcMethodType,
31+
service: serviceName,
32+
method: methodName,
33+
};
34+
}
35+
36+
async function* incrementStreamMessagesCounter<T>(iterable: AsyncIterable<T>, callback: () => void): AsyncIterable<T> {
37+
for await (const item of iterable) {
38+
callback();
39+
yield item;
40+
}
41+
}
42+
43+
export function prometheusClientMiddleware(metrics: IClientCallMetrics): ClientMiddleware {
44+
return async function* prometheusClientMiddlewareGenerator<Request, Response>(
45+
call: ClientMiddlewareCall<Request, Response>,
46+
options: CallOptions,
47+
): AsyncGenerator<Response, Response | void, undefined> {
48+
const labels = getLabels(call.method);
49+
50+
metrics.started(labels);
51+
52+
let settled = false;
53+
let status: Status = Status.OK;
54+
55+
try {
56+
let request;
57+
58+
if (!call.requestStream) {
59+
request = call.request;
60+
} else {
61+
request = incrementStreamMessagesCounter(call.request, metrics.sent.bind(metrics, labels));
62+
}
63+
64+
if (!call.responseStream) {
65+
const response = yield* call.next(request, options);
66+
settled = true;
67+
return response;
68+
} else {
69+
yield* incrementStreamMessagesCounter(
70+
call.next(request, options),
71+
metrics.received.bind(metrics, labels),
72+
);
73+
settled = true;
74+
return;
75+
}
76+
} catch (err) {
77+
settled = true;
78+
if (err instanceof ClientError) {
79+
status = err.code;
80+
} else if (isAbortError(err)) {
81+
status = Status.CANCELLED;
82+
} else {
83+
status = Status.UNKNOWN;
84+
}
85+
throw err;
86+
} finally {
87+
if (!settled) {
88+
status = Status.CANCELLED;
89+
}
90+
metrics.handled({ ...labels, code: Status[status] });
91+
}
92+
};
93+
}

components/server/src/container-module.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ import { LicenseEvaluator } from "@gitpod/licensor/lib";
101101
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace/workspace-cluster-imagebuilder-client-provider";
102102
import { UsageServiceClient, UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
103103
import { BillingServiceClient, BillingServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/billing.pb";
104-
import { createChannel, createClient } from "nice-grpc";
104+
import { createChannel, createClient, createClientFactory } from "nice-grpc";
105105
import { CommunityEntitlementService, EntitlementService } from "./billing/entitlement-service";
106106
import {
107107
ConfigCatClientFactory,
@@ -111,6 +111,7 @@ import { VerificationService } from "./auth/verification-service";
111111
import { WebhookEventGarbageCollector } from "./projects/webhook-event-garbage-collector";
112112
import { LivenessController } from "./liveness/liveness-controller";
113113
import { IDEServiceClient, IDEServiceDefinition } from "@gitpod/ide-service-api/lib/ide.pb";
114+
import { prometheusClientMiddleware } from "@gitpod/gitpod-protocol/lib/util/nice-grpc";
114115

115116
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
116117
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -271,7 +272,10 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
271272

272273
bind<IDEServiceClient>(IDEServiceDefinition.name).toDynamicValue((ctx) => {
273274
const config = ctx.container.get<Config>(Config);
274-
return createClient(IDEServiceDefinition, createChannel(config.ideServiceAddr));
275+
const metricsClient = ctx.container.get<IClientCallMetrics>(IClientCallMetrics);
276+
return createClientFactory()
277+
.use(prometheusClientMiddleware(metricsClient))
278+
.create(IDEServiceDefinition, createChannel(config.ideServiceAddr));
275279
});
276280

277281
bind(EntitlementService).to(CommunityEntitlementService).inSingletonScope();

0 commit comments

Comments
 (0)