Skip to content

Commit

Permalink
change endpoint to location-only
Browse files Browse the repository at this point in the history
  • Loading branch information
daniellacosse committed Sep 5, 2024
1 parent 40c8929 commit cf4383b
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 56 deletions.
26 changes: 9 additions & 17 deletions src/shadowbox/server/manager_metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ import {DataUsageByUser, DataUsageTimeframe} from '../model/metrics';

export type TunnelTimeDimension = 'access_key' | 'country' | 'asn';

interface TunneTimeRequest {
params: {
sinceUnixTimestamp: number;
dimensions?: TunnelTimeDimension[];
};
interface TunnelTimeRequest {
hours: number;
}

interface TunnelTimeResponse {
Expand All @@ -35,7 +32,7 @@ interface TunnelTimeResponse {

export interface ManagerMetrics {
getOutboundByteTransfer(timeframe: DataUsageTimeframe): Promise<DataUsageByUser>;
getTunnelTime(request: TunneTimeRequest): Promise<TunnelTimeResponse[]>;
getTunnelTimeByLocation(request: TunnelTimeRequest): Promise<TunnelTimeResponse[]>;
}

// Reads manager metrics from a Prometheus instance.
Expand All @@ -60,19 +57,14 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
return {bytesTransferredByUserId: usage};
}

async getTunnelTime({
params: {dimensions, sinceUnixTimestamp},
}: TunneTimeRequest): Promise<TunnelTimeResponse[]> {
const timeExpression = `[${Math.round(Date.now() / 1000) - sinceUnixTimestamp}s]`;
const dimensionsExpression =
dimensions && dimensions.length ? ` by (${dimensions.join()})` : '';
const prometheusQuery = `sum(increase(shadowsocks_tunnel_time_seconds${timeExpression}))${dimensionsExpression}`;
const {result} = await this.prometheusClient.query(prometheusQuery);
async getTunnelTimeByLocation({hours}: TunnelTimeRequest): Promise<TunnelTimeResponse[]> {
const {result} = await this.prometheusClient.query(
`sum(increase(shadowsocks_tunnel_time_seconds_per_location[${hours}h])) by (location, asn, asorg)`
);

return result.map((entry) => ({
access_key: entry.metric['access_key'],
country: entry.metric['country'],
asn: entry.metric['asn'] ? parseInt(entry.metric['asn']) : undefined,
location: entry.metric['location'],
asn: entry.metric['asn'] ? parseInt(entry.metric['asn'], 10) : undefined,
tunnel_time: {hours: Math.round(parseFloat(entry.value[1]) / 60 / 60)},
}));
}
Expand Down
45 changes: 6 additions & 39 deletions src/shadowbox/server/manager_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ export function bindService(
);

apiServer.get(`${apiPrefix}/metrics/transfer`, service.getDataUsage.bind(service));
apiServer.get(`${apiPrefix}/metrics/tunnel`, service.getTunnelTime.bind(service));
apiServer.get(
`${apiPrefix}/metrics/tunnel/location`,
service.getTunnelTimeByLocation.bind(service)
);
apiServer.get(`${apiPrefix}/metrics/enabled`, service.getShareMetrics.bind(service));
apiServer.put(`${apiPrefix}/metrics/enabled`, service.setShareMetrics.bind(service));

Expand Down Expand Up @@ -607,46 +610,10 @@ export class ShadowsocksManagerService {
}
}

async getTunnelTime(req: RequestType, res: ResponseType, next: restify.Next) {
async getTunnelTimeByLocation(req: RequestType, res: ResponseType, next: restify.Next) {
try {
logging.debug(`getTunnelTime request ${JSON.stringify(req.query)}`);
const sinceUnixTimestamp = parseInt(req.query.since as string, 10);
if (
typeof sinceUnixTimestamp !== 'number' ||
sinceUnixTimestamp <= 0 ||
isNaN(sinceUnixTimestamp) ||
sinceUnixTimestamp > Date.now() / 1000
) {
return next(
new restifyErrors.InvalidArgumentError(
{statusCode: 400},
'Parameter `since` must be a unix timestamp in the past. Got: ' + req.query.since
)
);
}
const dimensions = (req.query.dimensions as string)?.split(',');
if (!Array.isArray(dimensions)) {
return next(
new restifyErrors.InvalidArgumentError(
{statusCode: 400},
'Parameter `dimensions` must be an array. Got: ' + req.query.dimensions
)
);
}

if (dimensions.some((d) => d !== 'access_key' && d !== 'country' && d !== 'asn')) {
return next(
new restifyErrors.InvalidArgumentError(
{statusCode: 400},
'Parameter `dimensions` must be an array containing only "access_key", "country" and "asn". Got: ' +
req.query.dimensions
)
);
}

const response = await this.managerMetrics.getTunnelTime({
params: {sinceUnixTimestamp, dimensions: dimensions as TunnelTimeDimension[]},
});
const response = await this.managerMetrics.getTunnelTimeByLocation({hours: 30 * 24});
res.send(HttpSuccess.OK, response);
logging.debug(`getTunnelTime response ${JSON.stringify(response)}`);
return next();
Expand Down

0 comments on commit cf4383b

Please sign in to comment.