Skip to content

Commit 9d9f76b

Browse files
feat(server): unified metrics endpoint (#1616)
* draft of unified metrics endpoint * ok, empty state works * I think it's there? * prefix with 'fake' * some feedback * continue feedback * test refactor * remove unused imports * bad refactor * partial feedback * move time range parsing logic * full union * feedback
1 parent f558117 commit 9d9f76b

8 files changed

+433
-10
lines changed

src/shadowbox/infrastructure/prometheus_scraper.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ interface QueryResult {
3737
error: string;
3838
}
3939

40-
export class PrometheusClient {
40+
export interface PrometheusClient {
41+
query(query: string): Promise<QueryResultData>;
42+
}
43+
44+
export class ApiPrometheusClient implements PrometheusClient {
4145
constructor(private address: string) {}
4246

4347
query(query: string): Promise<QueryResultData> {
@@ -101,7 +105,7 @@ async function spawnPrometheusSubprocess(
101105
prometheusEndpoint: string
102106
): Promise<child_process.ChildProcess> {
103107
logging.info('======== Starting Prometheus ========');
104-
logging.info(`${binaryFilename} ${processArgs.map(a => `"${a}"`).join(' ')}`);
108+
logging.info(`${binaryFilename} ${processArgs.map((a) => `"${a}"`).join(' ')}`);
105109
const runProcess = child_process.spawn(binaryFilename, processArgs);
106110
runProcess.on('error', (error) => {
107111
logging.error(`Error spawning Prometheus: ${error}`);

src/shadowbox/server/api.yml

+54-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,60 @@ paths:
127127
responses:
128128
'204':
129129
description: Access key limit deleted successfully.
130-
130+
/experimental/server/metrics:
131+
get:
132+
tags: Server
133+
parameters:
134+
- in: query
135+
name: since
136+
description: the range of time to return data for
137+
schema:
138+
type: string
139+
responses:
140+
'200':
141+
description: Display server metric information
142+
content:
143+
application/json:
144+
schema:
145+
type: object
146+
properties:
147+
server:
148+
type: array
149+
items:
150+
type: object
151+
properties:
152+
location:
153+
type: string
154+
asn:
155+
type: number
156+
asOrg:
157+
type: string
158+
tunnelTime:
159+
type: object
160+
properties:
161+
seconds: number
162+
dataTransferred:
163+
type: object
164+
properties:
165+
bytes: number
166+
accessKeys:
167+
type: array
168+
items:
169+
type: object
170+
properties:
171+
accessKeyId:
172+
type: string
173+
tunnelTime:
174+
type: object
175+
properties:
176+
seconds: number
177+
dataTransferred:
178+
type: object
179+
properties:
180+
bytes: number
181+
examples:
182+
'0':
183+
value: '{"server":[{"location":"US","asn":null,"asOrg":null,"tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100}}],"accessKeys":[{"accessKeyId":"0","tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100}}]}'
131184
/name:
132185
put:
133186
description: Renames the server

src/shadowbox/server/main.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {RealClock} from '../infrastructure/clock';
2424
import {PortProvider} from '../infrastructure/get_port';
2525
import * as json_config from '../infrastructure/json_config';
2626
import * as logging from '../infrastructure/logging';
27-
import {PrometheusClient, startPrometheus} from '../infrastructure/prometheus_scraper';
27+
import {ApiPrometheusClient, startPrometheus} from '../infrastructure/prometheus_scraper';
2828
import {RolloutTracker} from '../infrastructure/rollout';
2929
import * as version from './version';
3030

@@ -197,7 +197,7 @@ async function main() {
197197
prometheusEndpoint
198198
);
199199

200-
const prometheusClient = new PrometheusClient(prometheusEndpoint);
200+
const prometheusClient = new ApiPrometheusClient(prometheusEndpoint);
201201
if (!serverConfig.data().portForNewAccessKeys) {
202202
serverConfig.data().portForNewAccessKeys = await portProvider.reserveNewPort();
203203
serverConfig.write();

src/shadowbox/server/manager_metrics.spec.ts

+191
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,200 @@
1313
// limitations under the License.
1414

1515
import {PrometheusManagerMetrics} from './manager_metrics';
16+
import {PrometheusClient, QueryResultData} from '../infrastructure/prometheus_scraper';
1617
import {FakePrometheusClient} from './mocks/mocks';
1718

19+
export class QueryMapPrometheusClient implements PrometheusClient {
20+
constructor(private queryMap: {[query: string]: QueryResultData}) {}
21+
22+
async query(_query: string): Promise<QueryResultData> {
23+
return this.queryMap[_query];
24+
}
25+
}
26+
1827
describe('PrometheusManagerMetrics', () => {
28+
it('getServerMetrics', async (done) => {
29+
const managerMetrics = new PrometheusManagerMetrics(
30+
new QueryMapPrometheusClient({
31+
'sum(increase(shadowsocks_data_bytes_per_location{dir=~"c<p|p>t"}[0s])) by (location, asn, asorg)':
32+
{
33+
resultType: 'vector',
34+
result: [
35+
{
36+
metric: {
37+
location: 'US',
38+
asn: '49490',
39+
asorg: null,
40+
},
41+
value: [null, '1000'],
42+
},
43+
],
44+
},
45+
'sum(increase(shadowsocks_tunnel_time_seconds_per_location[0s])) by (location, asn, asorg)':
46+
{
47+
resultType: 'vector',
48+
result: [
49+
{
50+
metric: {
51+
location: 'US',
52+
asn: '49490',
53+
asorg: null,
54+
},
55+
value: [null, '1000'],
56+
},
57+
],
58+
},
59+
'sum(increase(shadowsocks_data_bytes{dir=~"c<p|p>t"}[0s])) by (access_key)': {
60+
resultType: 'vector',
61+
result: [
62+
{
63+
metric: {
64+
access_key: '0',
65+
},
66+
value: [null, '1000'],
67+
},
68+
],
69+
},
70+
'sum(increase(shadowsocks_tunnel_time_seconds[0s])) by (access_key)': {
71+
resultType: 'vector',
72+
result: [
73+
{
74+
metric: {
75+
access_key: '0',
76+
},
77+
value: [null, '1000'],
78+
},
79+
],
80+
},
81+
})
82+
);
83+
84+
const serverMetrics = await managerMetrics.getServerMetrics({seconds: 0});
85+
86+
expect(JSON.stringify(serverMetrics, null, 2)).toEqual(`{
87+
"server": [
88+
{
89+
"location": "US",
90+
"asn": 49490,
91+
"asOrg": "null",
92+
"tunnelTime": {
93+
"seconds": 1000
94+
},
95+
"dataTransferred": {
96+
"bytes": 1000
97+
}
98+
}
99+
],
100+
"accessKeys": [
101+
{
102+
"accessKeyId": 0,
103+
"tunnelTime": {
104+
"seconds": 1000
105+
},
106+
"dataTransferred": {
107+
"bytes": 1000
108+
}
109+
}
110+
]
111+
}`);
112+
done();
113+
});
114+
115+
it('getServerMetrics - does a full outer join on metric data', async (done) => {
116+
const managerMetrics = new PrometheusManagerMetrics(
117+
new QueryMapPrometheusClient({
118+
'sum(increase(shadowsocks_data_bytes_per_location{dir=~"c<p|p>t"}[0s])) by (location, asn, asorg)':
119+
{
120+
resultType: 'vector',
121+
result: [
122+
{
123+
metric: {
124+
location: 'US',
125+
asn: '49490',
126+
asorg: null,
127+
},
128+
value: [null, '1000'],
129+
},
130+
],
131+
},
132+
'sum(increase(shadowsocks_tunnel_time_seconds_per_location[0s])) by (location, asn, asorg)':
133+
{
134+
resultType: 'vector',
135+
result: [
136+
{
137+
metric: {
138+
location: 'CA',
139+
asn: '53520',
140+
asorg: null,
141+
},
142+
value: [null, '1000'],
143+
},
144+
],
145+
},
146+
'sum(increase(shadowsocks_data_bytes{dir=~"c<p|p>t"}[0s])) by (access_key)': {
147+
resultType: 'vector',
148+
result: [
149+
{
150+
metric: {
151+
access_key: '0',
152+
},
153+
value: [null, '1000'],
154+
},
155+
],
156+
},
157+
'sum(increase(shadowsocks_tunnel_time_seconds[0s])) by (access_key)': {
158+
resultType: 'vector',
159+
result: [
160+
{
161+
metric: {
162+
access_key: '1',
163+
},
164+
value: [null, '1000'],
165+
},
166+
],
167+
},
168+
})
169+
);
170+
171+
const serverMetrics = await managerMetrics.getServerMetrics({seconds: 0});
172+
173+
expect(JSON.stringify(serverMetrics, null, 2)).toEqual(`{
174+
"server": [
175+
{
176+
"location": "CA",
177+
"asn": 53520,
178+
"asOrg": "null",
179+
"tunnelTime": {
180+
"seconds": 1000
181+
}
182+
},
183+
{
184+
"location": "US",
185+
"asn": 49490,
186+
"asOrg": "null",
187+
"dataTransferred": {
188+
"bytes": 1000
189+
}
190+
}
191+
],
192+
"accessKeys": [
193+
{
194+
"accessKeyId": 1,
195+
"tunnelTime": {
196+
"seconds": 1000
197+
}
198+
},
199+
{
200+
"accessKeyId": 0,
201+
"dataTransferred": {
202+
"bytes": 1000
203+
}
204+
}
205+
]
206+
}`);
207+
done();
208+
});
209+
19210
it('getOutboundByteTransfer', async (done) => {
20211
const managerMetrics = new PrometheusManagerMetrics(
21212
new FakePrometheusClient({'access-key-1': 1000, 'access-key-2': 10000})

0 commit comments

Comments
 (0)