diff --git a/src/shadowbox/server/api.yml b/src/shadowbox/server/api.yml index 2532d51c..4dc44ffb 100644 --- a/src/shadowbox/server/api.yml +++ b/src/shadowbox/server/api.yml @@ -208,7 +208,8 @@ paths: type: object properties: accessKeyId: - type: integer + description: Exact access key ID. + type: string tunnelTime: type: object properties: diff --git a/src/shadowbox/server/manager_metrics.spec.ts b/src/shadowbox/server/manager_metrics.spec.ts index ed3107ff..78bce70d 100644 --- a/src/shadowbox/server/manager_metrics.spec.ts +++ b/src/shadowbox/server/manager_metrics.spec.ts @@ -194,7 +194,7 @@ describe('PrometheusManagerMetrics', () => { }, "accessKeys": [ { - "accessKeyId": 0, + "accessKeyId": "0", "dataTransferred": { "bytes": 1000 }, @@ -380,7 +380,7 @@ describe('PrometheusManagerMetrics', () => { }, "accessKeys": [ { - "accessKeyId": 1, + "accessKeyId": "1", "dataTransferred": { "bytes": 0 }, @@ -396,7 +396,7 @@ describe('PrometheusManagerMetrics', () => { } }, { - "accessKeyId": 0, + "accessKeyId": "0", "dataTransferred": { "bytes": 1000 }, @@ -427,4 +427,33 @@ describe('PrometheusManagerMetrics', () => { expect(bytesTransferredByUserId['access-key-2']).toEqual(10000); done(); }); + + it('preserves custom access key IDs in server metrics', async () => { + const accessKeyIds = ['custom-id', '001', '9007199254740993']; + const prometheusClient = { + query: async (query: string): Promise => { + if (query.includes('shadowsocks_data_bytes{')) { + return { + resultType: 'vector', + result: accessKeyIds.map((accessKey, index) => ({ + metric: {access_key: accessKey}, + value: [1, String(index + 1)], + })), + }; + } + return {resultType: 'vector', result: []}; + }, + queryRange: async (query: string): Promise => { + if (query.includes('shadowsocks_data_bytes_per_location')) { + return {resultType: 'matrix', result: [{metric: {}, values: []}]}; + } + return {resultType: 'matrix', result: []}; + }, + } as PrometheusClient; + const managerMetrics = new PrometheusManagerMetrics(prometheusClient); + + const serverMetrics = await managerMetrics.getServerMetrics({seconds: 0}); + + expect(serverMetrics.accessKeys.map((entry) => entry.accessKeyId)).toEqual(accessKeyIds); + }); }); diff --git a/src/shadowbox/server/manager_metrics.ts b/src/shadowbox/server/manager_metrics.ts index 422f1162..981573f9 100644 --- a/src/shadowbox/server/manager_metrics.ts +++ b/src/shadowbox/server/manager_metrics.ts @@ -61,7 +61,7 @@ interface ServerMetricsLocationEntry { } interface ServerMetricsAccessKeyEntry { - accessKeyId: number; + accessKeyId: string; tunnelTime: Duration; dataTransferred: Data; connection: ConnectionStats; @@ -88,7 +88,8 @@ export class PrometheusManagerMetrics implements ManagerMetrics { const result = await this.prometheusClient.query( `sum(increase(shadowsocks_data_bytes{dir=~"ct"}[${timeframe.hours}h])) by (access_key)` ); - const usage = {} as {[userId: string]: number}; + // Custom IDs such as __proto__ must remain ordinary own properties. + const usage: {[userId: string]: number} = Object.create(null); for (const entry of result.result) { const bytes = Math.round(parseFloat(entry.value[1])); if (bytes === 0) { @@ -297,7 +298,7 @@ function getServerMetricsAccessKeyEntry( let entry = map.get(accessKey); if (entry === undefined) { entry = { - accessKeyId: parseInt(accessKey), + accessKeyId: accessKey, dataTransferred: {bytes: 0}, tunnelTime: {seconds: 0}, connection: {