Skip to content

Commit

Permalink
Merge branch 'sbruens/optin-asn' into sbruens/optin-metrics-tunneltime
Browse files Browse the repository at this point in the history
  • Loading branch information
sbruens committed Oct 17, 2024
2 parents 40df2a0 + 540b1e0 commit 915dff6
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 16 deletions.
74 changes: 65 additions & 9 deletions src/shadowbox/server/shared_metrics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {AccessKeyConfigJson} from './server_access_key';

import {ServerConfigJson} from './server_config';
import {
LocationUsage,
ReportedUsage,
DailyFeatureMetricsReportJson,
HourlyServerMetricsReportJson,
MetricsCollectorClient,
Expand Down Expand Up @@ -78,7 +78,7 @@ describe('OutlineSharedMetricsPublisher', () => {
);

publisher.startSharing();
usageMetrics.locationUsage = [
usageMetrics.reportedUsage = [
{country: 'AA', inboundBytes: 11, tunnelTimeSec: 99},
{country: 'BB', inboundBytes: 11, tunnelTimeSec: 88},
{country: 'CC', inboundBytes: 22, tunnelTimeSec: 77},
Expand All @@ -102,7 +102,7 @@ describe('OutlineSharedMetricsPublisher', () => {
});

startTime = clock.nowMs;
usageMetrics.locationUsage = [
usageMetrics.reportedUsage = [
{country: 'EE', inboundBytes: 44, tunnelTimeSec: 11},
{country: 'FF', inboundBytes: 55, tunnelTimeSec: 22},
];
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('OutlineSharedMetricsPublisher', () => {
);
publisher.startSharing();

usageMetrics.locationUsage = [
usageMetrics.reportedUsage = [
{country: 'DD', inboundBytes: 44, tunnelTimeSec: 11, asn: 999},
{country: 'EE', inboundBytes: 55, tunnelTimeSec: 22},
];
Expand All @@ -150,6 +150,62 @@ describe('OutlineSharedMetricsPublisher', () => {
publisher.stopSharing();
});

it('reports different ASNs in the same country correctly', async () => {
const clock = new ManualClock();
const serverConfig = new InMemoryConfig<ServerConfigJson>({serverId: 'server-id'});
const usageMetrics = new ManualUsageMetrics();
const metricsCollector = new FakeMetricsCollector();
const publisher = new OutlineSharedMetricsPublisher(
clock,
serverConfig,
null,
usageMetrics,
metricsCollector
);
publisher.startSharing();

usageMetrics.reportedUsage = [
{country: 'DD', asn: 999, tunnelTimeSec: 11, inboundBytes: 44},
{country: 'DD', asn: 888, tunnelTimeSec: 22, inboundBytes: 55},
];
clock.nowMs += 60 * 60 * 1000;
await clock.runCallbacks();

expect(metricsCollector.collectedServerUsageReport.userReports).toEqual([
{bytesTransferred: 44, tunnelTimeSec: 11, countries: ['DD'], asn: 999},
{bytesTransferred: 55, tunnelTimeSec: 22, countries: ['DD'], asn: 888},
]);
publisher.stopSharing();
});

it('reports the same ASNs across different countries correctly', async () => {
const clock = new ManualClock();
const serverConfig = new InMemoryConfig<ServerConfigJson>({serverId: 'server-id'});
const usageMetrics = new ManualUsageMetrics();
const metricsCollector = new FakeMetricsCollector();
const publisher = new OutlineSharedMetricsPublisher(
clock,
serverConfig,
null,
usageMetrics,
metricsCollector
);
publisher.startSharing();

usageMetrics.reportedUsage = [
{country: 'DD', asn: 999, tunnelTimeSec: 11, inboundBytes: 44},
{country: 'EE', asn: 999, tunnelTimeSec: 22, inboundBytes: 55},
];
clock.nowMs += 60 * 60 * 1000;
await clock.runCallbacks();

expect(metricsCollector.collectedServerUsageReport.userReports).toEqual([
{bytesTransferred: 44, tunnelTimeSec: 11, countries: ['DD'], asn: 999},
{bytesTransferred: 55, tunnelTimeSec: 22, countries: ['EE'], asn: 999},
]);
publisher.stopSharing();
});

it('ignores sanctioned countries', async () => {
const clock = new ManualClock();
const startTime = clock.nowMs;
Expand All @@ -165,7 +221,7 @@ describe('OutlineSharedMetricsPublisher', () => {
);

publisher.startSharing();
usageMetrics.locationUsage = [
usageMetrics.reportedUsage = [
{country: 'AA', tunnelTimeSec: 99, inboundBytes: 11},
{country: 'SY', tunnelTimeSec: 88, inboundBytes: 11},
{country: 'CC', tunnelTimeSec: 77, inboundBytes: 22},
Expand Down Expand Up @@ -286,13 +342,13 @@ class FakeMetricsCollector implements MetricsCollectorClient {
}

class ManualUsageMetrics implements UsageMetrics {
locationUsage = [] as LocationUsage[];
reportedUsage = [] as ReportedUsage[];

getLocationUsage(): Promise<LocationUsage[]> {
return Promise.resolve(this.locationUsage);
getReportedUsage(): Promise<ReportedUsage[]> {
return Promise.resolve(this.reportedUsage);
}

reset() {
this.locationUsage = [] as LocationUsage[];
this.reportedUsage = [] as ReportedUsage[];
}
}
14 changes: 7 additions & 7 deletions src/shadowbox/server/shared_metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const MS_PER_HOUR = 60 * 60 * 1000;
const MS_PER_DAY = 24 * MS_PER_HOUR;
const SANCTIONED_COUNTRIES = new Set(['CU', 'KP', 'SY']);

export interface LocationUsage {
export interface ReportedUsage {
country: string;
asn?: number;
inboundBytes: number;
Expand Down Expand Up @@ -74,7 +74,7 @@ export interface SharedMetricsPublisher {
}

export interface UsageMetrics {
getLocationUsage(): Promise<LocationUsage[]>;
getReportedUsage(): Promise<ReportedUsage[]>;
reset();
}

Expand All @@ -84,7 +84,7 @@ export class PrometheusUsageMetrics implements UsageMetrics {

constructor(private prometheusClient: PrometheusClient) {}

async getLocationUsage(): Promise<LocationUsage[]> {
async getReportedUsage(): Promise<ReportedUsage[]> {
const timeDeltaSecs = Math.round((Date.now() - this.resetTimeMs) / 1000);
// Return both data bytes and tunnel time information with a single
// Prometheus query, by using a custom "metric_type" label.
Expand All @@ -101,14 +101,14 @@ export class PrometheusUsageMetrics implements UsageMetrics {
)
`);

const usage = new Map<string, LocationUsage>();
const usage = new Map<string, ReportedUsage>();
for (const result of queryResponse.result) {
const country = result.metric['location'] || '';
const asn = result.metric['asn'] ? Number(result.metric['asn']) : undefined;

// Get or create an entry for the country+ASN combination.
const key = `${country}-${asn}`;
let entry: LocationUsage;
let entry: ReportedUsage;
if (usage.has(key)) {
entry = usage.get(key);
} else {
Expand Down Expand Up @@ -198,7 +198,7 @@ export class OutlineSharedMetricsPublisher implements SharedMetricsPublisher {
return;
}
try {
await this.reportServerUsageMetrics(await usageMetrics.getLocationUsage());
await this.reportServerUsageMetrics(await usageMetrics.getReportedUsage());
usageMetrics.reset();
} catch (err) {
logging.error(`Failed to report server usage metrics: ${err}`);
Expand Down Expand Up @@ -232,7 +232,7 @@ export class OutlineSharedMetricsPublisher implements SharedMetricsPublisher {
return this.serverConfig.data().metricsEnabled || false;
}

private async reportServerUsageMetrics(locationUsageMetrics: LocationUsage[]): Promise<void> {
private async reportServerUsageMetrics(locationUsageMetrics: ReportedUsage[]): Promise<void> {
const reportEndTimestampMs = this.clock.now();

const userReports: HourlyUserMetricsReportJson[] = [];
Expand Down

0 comments on commit 915dff6

Please sign in to comment.