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

Commit

Permalink
example: using prometheus tags in custom metrics (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathetake authored Feb 4, 2022
1 parent af57b89 commit 86c2a56
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 57 deletions.
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)
}

0 comments on commit 86c2a56

Please sign in to comment.