diff --git a/cmd/outline-ss-server/metrics.go b/cmd/outline-ss-server/metrics.go index 8c8ce403..a5c1bae9 100644 --- a/cmd/outline-ss-server/metrics.go +++ b/cmd/outline-ss-server/metrics.go @@ -31,6 +31,8 @@ const ( activeIPKeyTrackerReportingInterval = 5 * time.Second ) +var since = time.Since + type outlineMetrics struct { ipinfo.IPInfoMap activeIPKeyTracker @@ -93,7 +95,7 @@ func (t *activeIPKeyTracker) reportAll() { // Reports time connected for a given active client. func (t *activeIPKeyTracker) reportDuration(c activeClient) { - connDuration := time.Since(c.startTime) + connDuration := since(c.startTime) logger.Debugf("Reporting activity for key `%v`, duration: %v", c.IPKey.accessKey, connDuration) t.metricsCallback(c.IPKey, connDuration) diff --git a/cmd/outline-ss-server/metrics_test.go b/cmd/outline-ss-server/metrics_test.go index 0663861f..9feef6af 100644 --- a/cmd/outline-ss-server/metrics_test.go +++ b/cmd/outline-ss-server/metrics_test.go @@ -15,12 +15,14 @@ package main import ( + "strings" "testing" "time" "github.com/Jigsaw-Code/outline-ss-server/ipinfo" "github.com/Jigsaw-Code/outline-ss-server/service/metrics" "github.com/prometheus/client_golang/prometheus" + promtest "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" ) @@ -40,6 +42,7 @@ func TestMethodsDontPanic(t *testing.T) { ssMetrics.SetBuildInfo("0.0.0-test") ssMetrics.SetNumAccessKeys(20, 2) ssMetrics.AddOpenTCPConnection(fakeAddr("127.0.0.1:9")) + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:9"), "key-1") ssMetrics.AddClosedTCPConnection(fakeAddr("127.0.0.1:9"), "1", "OK", proxyMetrics, 10*time.Millisecond) ssMetrics.AddUDPPacketFromClient(ipinfo.IPInfo{CountryCode: "US", ASN: 100}, "2", "OK", 10, 20) ssMetrics.AddUDPPacketFromTarget(ipinfo.IPInfo{CountryCode: "US", ASN: 100}, "3", "OK", 10, 20) @@ -55,6 +58,79 @@ func TestASNLabel(t *testing.T) { require.Equal(t, "100", asnLabel(100)) } +func TestIPKeyActivityPerKeyDoesNotReportUnlessAllConnectionsClosed(t *testing.T) { + since = func(time.Time) time.Duration { return 3 * time.Second } + reg := prometheus.NewPedanticRegistry() + ssMetrics := newPrometheusOutlineMetrics(nil, reg) + accessKey := "key-1" + status := "OK" + data := metrics.ProxyMetrics{} + duration := time.Minute + + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey) + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:1"), accessKey) + ssMetrics.AddClosedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey, status, data, duration) + + err := promtest.GatherAndCompare( + reg, + strings.NewReader(""), + "shadowsocks_ip_key_connectivity_seconds", + ) + require.NoError(t, err, "unexpectedly found metric value") +} + +func TestIPKeyActivityPerKey(t *testing.T) { + since = func(time.Time) time.Duration { return 3 * time.Second } + reg := prometheus.NewPedanticRegistry() + ssMetrics := newPrometheusOutlineMetrics(nil, reg) + accessKey := "key-1" + status := "OK" + data := metrics.ProxyMetrics{} + duration := time.Minute + + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey) + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:1"), accessKey) + ssMetrics.AddClosedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey, status, data, duration) + ssMetrics.AddClosedTCPConnection(fakeAddr("127.0.0.1:1"), accessKey, status, data, duration) + + expected := strings.NewReader(` + # HELP shadowsocks_ip_key_connectivity_seconds Time at least 1 connection was open for a (IP, access key) pair, per key + # TYPE shadowsocks_ip_key_connectivity_seconds counter + shadowsocks_ip_key_connectivity_seconds{access_key="key-1"} 3 +`) + err := promtest.GatherAndCompare( + reg, + expected, + "shadowsocks_ip_key_connectivity_seconds", + ) + require.NoError(t, err, "unexpected metric value found") +} + +func TestIPKeyActivityPerLocation(t *testing.T) { + since = func(time.Time) time.Duration { return 5 * time.Second } + reg := prometheus.NewPedanticRegistry() + ssMetrics := newPrometheusOutlineMetrics(nil, reg) + accessKey := "key-1" + status := "OK" + data := metrics.ProxyMetrics{} + duration := time.Minute + + ssMetrics.AddAuthenticatedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey) + ssMetrics.AddClosedTCPConnection(fakeAddr("127.0.0.1:9"), accessKey, status, data, duration) + + expected := strings.NewReader(` + # HELP shadowsocks_ip_key_connectivity_seconds_per_location Time at least 1 connection was open for a (IP, access key) pair, per location + # TYPE shadowsocks_ip_key_connectivity_seconds_per_location counter + shadowsocks_ip_key_connectivity_seconds_per_location{asn="",location=""} 5 +`) + err := promtest.GatherAndCompare( + reg, + expected, + "shadowsocks_ip_key_connectivity_seconds_per_location", + ) + require.NoError(t, err, "unexpected metric value found") +} + func BenchmarkOpenTCP(b *testing.B) { ssMetrics := newPrometheusOutlineMetrics(nil, prometheus.NewRegistry()) b.ResetTimer()