Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

example: using prometheus tags in custom metrics #246

Merged
merged 4 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
21 changes: 4 additions & 17 deletions examples/metrics/README.md
Original file line number Diff line number Diff line change
@@ -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
```
10 changes: 10 additions & 0 deletions examples/metrics/envoy.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
35 changes: 24 additions & 11 deletions examples/metrics/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
}
9 changes: 3 additions & 6 deletions examples/metrics/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}