diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index fd3b8fbc..b57532ad 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -169,29 +169,44 @@ func Test_http_routing(t *testing.T) { func Test_metrics(t *testing.T) { _, kill := startEnvoy(t, 8001) defer kill() - var count int - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil { - return false - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return false - } - count++ - return count == 10 - }, 5*time.Second, time.Millisecond, "Endpoint not healthy.") - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:8001/stats/prometheus") - if err != nil { - return false - } - defer res.Body.Close() - raw, err := io.ReadAll(res.Body) - require.NoError(t, err) - return checkMessage(string(raw), []string{fmt.Sprintf("proxy_wasm_go_request_counter{} %d", count)}, nil) - }, 5*time.Second, time.Millisecond, "Expected stats not found") + + const customHeaderKey = "my-custom-header" + customHeaderToExpectedCounts := map[string]int{ + "foo": 3, + "bar": 5, + } + for headerValue, expCount := range customHeaderToExpectedCounts { + var actualCount int + require.Eventually(t, func() bool { + req, err := http.NewRequest("GET", "http://localhost:18000", nil) + require.NoError(t, err) + req.Header.Add(customHeaderKey, headerValue) + res, err := http.DefaultClient.Do(req) + if err != nil { + return false + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return false + } + actualCount++ + return actualCount == expCount + }, 5*time.Second, time.Millisecond, "Endpoint not healthy.") + } + + for headerValue, expCount := range customHeaderToExpectedCounts { + expectedMetric := fmt.Sprintf("custom_header_value_counts{value=\"%s\",reporter=\"wasmgosdk\"} %d", headerValue, expCount) + require.Eventually(t, func() bool { + res, err := http.Get("http://localhost:8001/stats/prometheus") + if err != nil { + return false + } + defer res.Body.Close() + raw, err := io.ReadAll(res.Body) + require.NoError(t, err) + return checkMessage(string(raw), []string{expectedMetric}, nil) + }, 5*time.Second, time.Millisecond, "Expected stats not found") + } } func Test_network(t *testing.T) { diff --git a/examples/metrics/README.md b/examples/metrics/README.md index fd5b6319..c4823ad4 100644 --- a/examples/metrics/README.md +++ b/examples/metrics/README.md @@ -1,25 +1,12 @@ ## metrics -this example creates simple request counter +this example creates simple request counter with prometheus tags. ``` -wasm log: previous value of proxy_wasm_go.request_counter: 0 -wasm log: incremented -wasm log: previous value of proxy_wasm_go.request_counter: 1 -wasm log: incremented -wasm log: previous value of proxy_wasm_go.request_counter: 2 -wasm log: incremented -wasm log: previous value of proxy_wasm_go.request_counter: 3 -wasm log: incremented -wasm log: previous value of proxy_wasm_go.request_counter: 4 -wasm log: incremented -wasm log: previous value of proxy_wasm_go.request_counter: 5 -wasm log: incremented -``` +$ curl localhost:18000 -v -H "my-custom-header: foo" -``` $ curl -s 'localhost:8001/stats/prometheus'| grep proxy -# TYPE proxy_wasm_go_request_counter counter -proxy_wasm_go_request_counter{} 5 +# TYPE custom_header_value_counts counter +custom_header_value_counts{value="foo",reporter="wasmgosdk"} 1 ``` diff --git a/examples/metrics/envoy.yaml b/examples/metrics/envoy.yaml index 15c6cbbf..1b0a0b20 100644 --- a/examples/metrics/envoy.yaml +++ b/examples/metrics/envoy.yaml @@ -1,3 +1,13 @@ +stats_config: + stats_tags: + # Envoy extracs the first matching group as a value. + # This case, the part ([a-zA-Z]+) is extracted as a value for "value" tag. + # See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig. + - tag_name: value + regex: '(_value=([a-zA-Z]+))' + - tag_name: reporter + regex: '(_reporter=([a-zA-Z]+))' + static_resources: listeners: - name: main diff --git a/examples/metrics/main.go b/examples/metrics/main.go index 48ba4915..f502f07e 100644 --- a/examples/metrics/main.go +++ b/examples/metrics/main.go @@ -15,6 +15,8 @@ package main import ( + "fmt" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" ) @@ -31,36 +33,47 @@ type vmContext struct { // Override types.DefaultVMContext. func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { - return &metricPluginContext{ - counter: proxywasm.DefineCounterMetric("proxy_wasm_go.request_counter"), - } + return &metricPluginContext{} } type metricPluginContext struct { // Embed the default plugin context here, // so that we don't need to reimplement all the methods. types.DefaultPluginContext - counter proxywasm.MetricCounter } // Override types.DefaultPluginContext. func (ctx *metricPluginContext) NewHttpContext(contextID uint32) types.HttpContext { - return &metricHttpContext{counter: ctx.counter} + return &metricHttpContext{} } type metricHttpContext struct { // Embed the default http context here, // so that we don't need to reimplement all the methods. types.DefaultHttpContext - counter proxywasm.MetricCounter } +const ( + customHeaderKey = "my-custom-header" + customHeaderValueTagKey = "value" +) + +var counters = map[string]proxywasm.MetricCounter{} + // Override types.DefaultHttpContext. func (ctx *metricHttpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { - prev := ctx.counter.Value() - proxywasm.LogInfof("previous value of %s: %d", "proxy_wasm_go.request_counter", prev) - - ctx.counter.Increment(1) - proxywasm.LogInfo("incremented") + customHeaderValue, err := proxywasm.GetHttpRequestHeader(customHeaderKey) + if err == nil { + counter, ok := counters[customHeaderValue] + if !ok { + // This metric is processed as: custom_header_value_counts{value="foo",reporter="wasmgosdk"} n. + // The extraction rule is defined in envoy.yaml as a bootstrap configuration. + // See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig. + fqn := fmt.Sprintf("custom_header_value_counts_%s=%s_reporter=wasmgosdk", customHeaderValueTagKey, customHeaderValue) + counter = proxywasm.DefineCounterMetric(fqn) + counters[customHeaderValue] = counter + } + counter.Increment(1) + } return types.ActionContinue } diff --git a/examples/metrics/main_test.go b/examples/metrics/main_test.go index 00667f14..bbcb4a6b 100644 --- a/examples/metrics/main_test.go +++ b/examples/metrics/main_test.go @@ -25,20 +25,17 @@ func TestMetric(t *testing.T) { require.Equal(t, types.OnVMStartStatusOK, host.StartVM()) // Initialize http context. + headers := [][2]string{{"my-custom-header", "foo"}} contextID := host.InitializeHttpContext() exp := uint64(3) for i := uint64(0); i < exp; i++ { // Call OnRequestHeaders - action := host.CallOnRequestHeaders(contextID, nil, false) + action := host.CallOnRequestHeaders(contextID, headers, false) require.Equal(t, types.ActionContinue, action) } - // Check Envoy logs. - logs := host.GetInfoLogs() - require.Contains(t, logs, "incremented") - // Check metrics. - value, err := host.GetCounterMetric("proxy_wasm_go.request_counter") + value, err := host.GetCounterMetric("custom_header_value_counts_value=foo_reporter=wasmgosdk") require.NoError(t, err) require.Equal(t, uint64(3), value) }