Skip to content

Commit edff631

Browse files
committed
Added metrics support with an example and corresponding tests.
Signed-off-by: Hiram Chirino <hiram@hiramchirino.com>
1 parent 7678e8d commit edff631

File tree

11 files changed

+362
-0
lines changed

11 files changed

+362
-0
lines changed

src/main/java/io/roastedroot/proxywasm/impl/Imports.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.roastedroot.proxywasm.v1.Handler;
1111
import io.roastedroot.proxywasm.v1.LogLevel;
1212
import io.roastedroot.proxywasm.v1.MapType;
13+
import io.roastedroot.proxywasm.v1.MetricType;
1314
import io.roastedroot.proxywasm.v1.StreamType;
1415
import io.roastedroot.proxywasm.v1.WasmException;
1516
import io.roastedroot.proxywasm.v1.WasmResult;
@@ -889,4 +890,50 @@ int proxyCallForeignFunction(
889890
return e.result().getValue();
890891
}
891892
}
893+
894+
@WasmExport
895+
int proxyDefineMetric(int metricType, int nameDataPtr, int nameSize, int returnMetricId) {
896+
try {
897+
MetricType type = MetricType.fromInt(metricType);
898+
if (type == null) {
899+
return WasmResult.BAD_ARGUMENT.getValue();
900+
}
901+
902+
var name = string(readMemory(nameDataPtr, nameSize));
903+
int metricId = handler.defineMetric(type, name);
904+
putUint32(returnMetricId, metricId);
905+
return WasmResult.OK.getValue();
906+
} catch (WasmException e) {
907+
return e.result().getValue();
908+
}
909+
}
910+
911+
@WasmExport
912+
int proxyRecordMetric(int metricId, long value) {
913+
WasmResult result = handler.recordMetric(metricId, value);
914+
return result.getValue();
915+
}
916+
917+
@WasmExport
918+
int proxyRemoveMetric(int metricId) {
919+
WasmResult result = handler.removeMetric(metricId);
920+
return result.getValue();
921+
}
922+
923+
@WasmExport
924+
int proxyIncrementMetric(int metricId, long value) {
925+
WasmResult result = handler.incrementMetric(metricId, value);
926+
return result.getValue();
927+
}
928+
929+
@WasmExport
930+
int proxyGetMetric(int metricId, int returnValuePtr) {
931+
try {
932+
var result = handler.getMetric(metricId);
933+
putUint32(returnValuePtr, (int) result);
934+
return WasmResult.OK.getValue();
935+
} catch (WasmException e) {
936+
return e.result().getValue();
937+
}
938+
}
892939
}

src/main/java/io/roastedroot/proxywasm/v1/ChainedHandler.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,29 @@ public int dispatchHttpCall(
281281
public byte[] callForeignFunction(String name, byte[] bytes) throws WasmException {
282282
return next().callForeignFunction(name, bytes);
283283
}
284+
285+
@Override
286+
public int defineMetric(MetricType metricType, String name) throws WasmException {
287+
return next().defineMetric(metricType, name);
288+
}
289+
290+
@Override
291+
public WasmResult removeMetric(int metricId) {
292+
return next().removeMetric(metricId);
293+
}
294+
295+
@Override
296+
public WasmResult recordMetric(int metricId, long value) {
297+
return next().recordMetric(metricId, value);
298+
}
299+
300+
@Override
301+
public WasmResult incrementMetric(int metricId, long value) {
302+
return next().incrementMetric(metricId, value);
303+
}
304+
305+
@Override
306+
public long getMetric(int metricId) throws WasmException {
307+
return next().getMetric(metricId);
308+
}
284309
}

src/main/java/io/roastedroot/proxywasm/v1/Handler.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,4 +410,24 @@ default int dispatchHttpCall(
410410
default byte[] callForeignFunction(String name, byte[] bytes) throws WasmException {
411411
throw new WasmException(WasmResult.NOT_FOUND);
412412
}
413+
414+
default int defineMetric(MetricType metricType, String name) throws WasmException {
415+
throw new WasmException(WasmResult.UNIMPLEMENTED);
416+
}
417+
418+
default WasmResult removeMetric(int metricId) {
419+
return WasmResult.UNIMPLEMENTED;
420+
}
421+
422+
default WasmResult recordMetric(int metricId, long value) {
423+
return WasmResult.UNIMPLEMENTED;
424+
}
425+
426+
default WasmResult incrementMetric(int metricId, long value) {
427+
return WasmResult.UNIMPLEMENTED;
428+
}
429+
430+
default long getMetric(int metricId) throws WasmException {
431+
throw new WasmException(WasmResult.UNIMPLEMENTED);
432+
}
413433
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.roastedroot.proxywasm.v1;
2+
3+
/**
4+
* Represents the type of metric in proxy WASM.
5+
*/
6+
public enum MetricType {
7+
COUNTER(0),
8+
GAUGE(1),
9+
HISTOGRAM(2);
10+
11+
private final int value;
12+
13+
/**
14+
* Constructor for MetricType enum.
15+
*
16+
* @param value The integer value of the metric type
17+
*/
18+
MetricType(int value) {
19+
this.value = value;
20+
}
21+
22+
/**
23+
* Get the integer value of this metric type.
24+
*
25+
* @return The integer value
26+
*/
27+
public int getValue() {
28+
return value;
29+
}
30+
31+
/**
32+
* Convert an integer value to a MetricType.
33+
*
34+
* @param value The integer value to convert
35+
* @return The corresponding MetricType or null if the value doesn't match any MetricType
36+
*/
37+
public static MetricType fromInt(int value) {
38+
for (MetricType type : values()) {
39+
if (type.value == value) {
40+
return type;
41+
}
42+
}
43+
return null;
44+
}
45+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Attribution
2+
3+
This example originally came from:
4+
https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/ab4161dcf9246a828008b539a82a1556cf0f2e24/examples/metrics
5+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/proxy-wasm/proxy-wasm-go-sdk/examples/metrics
2+
3+
go 1.24
4+
5+
require github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI=
6+
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE=
7+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
8+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020-2024 Tetrate
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
21+
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
22+
)
23+
24+
func main() {}
25+
func init() {
26+
proxywasm.SetVMContext(&vmContext{})
27+
}
28+
29+
// vmContext implements types.VMContext.
30+
type vmContext struct {
31+
// Embed the default VM context here,
32+
// so that we don't need to reimplement all the methods.
33+
types.DefaultVMContext
34+
}
35+
36+
// NewPluginContext implements types.VMContext.
37+
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
38+
return &metricPluginContext{}
39+
}
40+
41+
// metricPluginContext implements types.PluginContext.
42+
type metricPluginContext struct {
43+
// Embed the default plugin context here,
44+
// so that we don't need to reimplement all the methods.
45+
types.DefaultPluginContext
46+
}
47+
48+
// NewHttpContext implements types.PluginContext.
49+
func (ctx *metricPluginContext) NewHttpContext(contextID uint32) types.HttpContext {
50+
return &metricHttpContext{}
51+
}
52+
53+
// metricHttpContext implements types.HttpContext.
54+
type metricHttpContext struct {
55+
// Embed the default http context here,
56+
// so that we don't need to reimplement all the methods.
57+
types.DefaultHttpContext
58+
}
59+
60+
const (
61+
customHeaderKey = "my-custom-header"
62+
customHeaderValueTagKey = "value"
63+
)
64+
65+
// counters is a map from custom header value to a counter metric.
66+
// Note that Proxy-Wasm plugins are single threaded, so no need to use a lock.
67+
var counters = map[string]proxywasm.MetricCounter{}
68+
69+
// OnHttpRequestHeaders implements types.HttpContext.
70+
func (ctx *metricHttpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
71+
customHeaderValue, err := proxywasm.GetHttpRequestHeader(customHeaderKey)
72+
if err == nil {
73+
counter, ok := counters[customHeaderValue]
74+
if !ok {
75+
// This metric is processed as: custom_header_value_counts{value="foo",reporter="wasmgosdk"} n.
76+
// The extraction rule is defined in envoy.yaml as a bootstrap configuration.
77+
// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig.
78+
fqn := fmt.Sprintf("custom_header_value_counts_%s=%s_reporter=wasmgosdk", customHeaderValueTagKey, customHeaderValue)
79+
counter = proxywasm.DefineCounterMetric(fqn)
80+
counters[customHeaderValue] = counter
81+
}
82+
counter.Increment(1)
83+
}
84+
return types.ActionContinue
85+
}
2.39 MB
Binary file not shown.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.roastedroot.proxywasm;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import com.dylibso.chicory.wasm.Parser;
7+
import io.roastedroot.proxywasm.v1.Action;
8+
import io.roastedroot.proxywasm.v1.MetricType;
9+
import io.roastedroot.proxywasm.v1.ProxyWasm;
10+
import io.roastedroot.proxywasm.v1.StartException;
11+
import java.nio.file.Path;
12+
import java.util.Map;
13+
import org.junit.jupiter.api.Test;
14+
15+
/**
16+
* Java port of https://github.com/proxy-wasm/proxy-wasm-go-sdk/blob/ab4161dcf9246a828008b539a82a1556cf0f2e24/examples/metrics/main_test.go
17+
*/
18+
public class MetricsTest {
19+
20+
@Test
21+
public void testMetric() throws StartException {
22+
var handler = new MockHandler();
23+
var module = Parser.parse(Path.of("./src/test/go-examples/metrics/main.wasm"));
24+
ProxyWasm.Builder builder = ProxyWasm.builder().withPluginHandler(handler);
25+
26+
try (var host = builder.build(module)) {
27+
try (var context = host.createHttpContext(handler)) {
28+
// Create headers with custom header
29+
Map<String, String> headers = Map.of("my-custom-header", "foo");
30+
31+
// Call OnRequestHeaders multiple times
32+
long expectedCount = 3;
33+
for (int i = 0; i < expectedCount; i++) {
34+
handler.setHttpRequestHeaders(headers);
35+
Action action = context.callOnRequestHeaders(false);
36+
assertEquals(Action.CONTINUE, action);
37+
}
38+
39+
// Check metrics
40+
var metric =
41+
handler.getMetric(
42+
"custom_header_value_counts_value=foo_reporter=wasmgosdk");
43+
assertNotNull(metric);
44+
assertEquals(MetricType.COUNTER, metric.type);
45+
assertEquals(expectedCount, metric.value);
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)