From 52f984a513a43bb0aed7572c8dbfedd51f970579 Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Sun, 8 Jun 2025 23:35:07 +0200 Subject: [PATCH] better rpcserver example, with some profiling/load testing --- .gitignore | 3 +- examples/rpcserver/README.md | 29 ++++++++ examples/rpcserver/main.go | 86 ++++++++++++++++++++++-- examples/rpcserver/rpc-payload-fast.json | 6 ++ examples/rpcserver/rpc-payload-slow.json | 6 ++ examples/rpcserver/targets.txt | 3 + 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 examples/rpcserver/README.md create mode 100644 examples/rpcserver/rpc-payload-fast.json create mode 100644 examples/rpcserver/rpc-payload-slow.json create mode 100644 examples/rpcserver/targets.txt diff --git a/.gitignore b/.gitignore index d19803d..0c1b484 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ # IDE .idea/ .vscode/ -cert.pem \ No newline at end of file +cert.pem +results.bin diff --git a/examples/rpcserver/README.md b/examples/rpcserver/README.md new file mode 100644 index 0000000..99d61b8 --- /dev/null +++ b/examples/rpcserver/README.md @@ -0,0 +1,29 @@ +Example RPC Server usage. + +- Implement a simple RPC server +- Handle requests with different processing times and gc-heavy operations +- Use pprof for profiling +- Use [Vegeta](https://github.com/tsenart/vegeta) for load testing + +Getting started: + +```bash +cd examples/rpcserver + +# Run the RPC server +go run main.go + +# Example requests +curl 'http://localhost:8080' --header 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"slow","params":[],"id":2}' + +# Using packaged payloads +curl 'http://localhost:8080' --header 'Content-Type: application/json' --data "@rpc-payload-fast.json" +curl 'http://localhost:8080' --header 'Content-Type: application/json' --data "@rpc-payload-slow.json" + +# Load testing with Vegeta +vegeta attack -rate=10000 -duration=60s -targets=targets.txt | tee results.bin | vegeta report + +# Grab pprof profiles +go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 +go tool pprof http://localhost:6060/debug/pprof/heap +``` diff --git a/examples/rpcserver/main.go b/examples/rpcserver/main.go index 97c9b8a..18dcb7a 100644 --- a/examples/rpcserver/main.go +++ b/examples/rpcserver/main.go @@ -1,30 +1,65 @@ package main +// +// This example demonstrates how to use the rpcserver package to create a simple JSON-RPC server. +// +// It includes profiling test handlers, inspired by https://goperf.dev/02-networking/bench-and-load +// + import ( "context" + "flag" "fmt" + "log/slog" + "math/rand/v2" "net/http" + "os" + "time" + + _ "net/http/pprof" "github.com/flashbots/go-utils/rpcserver" ) -var listenAddr = ":8080" +var ( + // Servers + listenAddr = "localhost:8080" + pprofAddr = "localhost:6060" + + // Logger for the server + log = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + + // Profiling utilities + fastDelay = flag.Duration("fast-delay", 0, "Fixed delay for fast handler (if any)") + slowMin = flag.Duration("slow-min", 1*time.Millisecond, "Minimum delay for slow handler") + slowMax = flag.Duration("slow-max", 300*time.Millisecond, "Maximum delay for slow handler") + gcMinAlloc = flag.Int("gc-min-alloc", 50, "Minimum number of allocations in GC heavy handler") + gcMaxAlloc = flag.Int("gc-max-alloc", 1000, "Maximum number of allocations in GC heavy handler") + longLivedData [][]byte +) func main() { handler, err := rpcserver.NewJSONRPCHandler( rpcserver.Methods{ - "test_foo": HandleTestFoo, + "test_foo": rpcHandlerTestFoo, + "fast": rpcHandlerFast, + "slow": rpcHandlerSlow, + "gc": rpcHandlerGCHeavy, }, rpcserver.JSONRPCHandlerOpts{ + Log: log, ServerName: "public_server", - GetResponseContent: []byte("Hello world"), + GetResponseContent: []byte("static GET content hurray \\o/\n"), }, ) if err != nil { panic(err) } - // server + // Start separate pprof server + go startPprofServer() + + // API server server := &http.Server{ Addr: listenAddr, Handler: handler, @@ -35,6 +70,47 @@ func main() { } } -func HandleTestFoo(ctx context.Context) (string, error) { +func startPprofServer() { + fmt.Println("Starting pprof server.", "pprofAddr:", pprofAddr) + if err := http.ListenAndServe(pprofAddr, nil); err != nil { + fmt.Println("Error starting pprof server:", err) + } +} + +func randRange(min, max int) int { + return rand.IntN(max-min) + min +} + +func rpcHandlerTestFoo(ctx context.Context) (string, error) { return "foo", nil } + +func rpcHandlerFast(ctx context.Context) (string, error) { + if *fastDelay > 0 { + time.Sleep(*fastDelay) + } + + return "fast response", nil +} + +func rpcHandlerSlow(ctx context.Context) (string, error) { + delayRange := int((*slowMax - *slowMin) / time.Millisecond) + delay := time.Duration(randRange(1, delayRange)) * time.Millisecond + time.Sleep(delay) + + return fmt.Sprintf("slow response with delay %d ms", delay.Milliseconds()), nil +} + +func rpcHandlerGCHeavy(ctx context.Context) (string, error) { + numAllocs := randRange(*gcMinAlloc, *gcMaxAlloc) + var data [][]byte + for i := 0; i < numAllocs; i++ { + // Allocate 10KB slices. Occasionally retain a reference to simulate long-lived objects. + b := make([]byte, 1024*10) + data = append(data, b) + if i%100 == 0 { // every 100 allocations, keep the data alive + longLivedData = append(longLivedData, b) + } + } + return fmt.Sprintf("allocated %d KB\n", len(data)*10), nil +} diff --git a/examples/rpcserver/rpc-payload-fast.json b/examples/rpcserver/rpc-payload-fast.json new file mode 100644 index 0000000..dde103f --- /dev/null +++ b/examples/rpcserver/rpc-payload-fast.json @@ -0,0 +1,6 @@ +{ + "jsonrpc": "2.0", + "method": "fast", + "params": [], + "id": 83 +} \ No newline at end of file diff --git a/examples/rpcserver/rpc-payload-slow.json b/examples/rpcserver/rpc-payload-slow.json new file mode 100644 index 0000000..24ab9fc --- /dev/null +++ b/examples/rpcserver/rpc-payload-slow.json @@ -0,0 +1,6 @@ +{ + "jsonrpc": "2.0", + "method": "slow", + "params": [], + "id": 83 +} \ No newline at end of file diff --git a/examples/rpcserver/targets.txt b/examples/rpcserver/targets.txt new file mode 100644 index 0000000..3aa5089 --- /dev/null +++ b/examples/rpcserver/targets.txt @@ -0,0 +1,3 @@ +POST http://localhost:8080 +Content-Type: application/json +@rpc-payload-slow.json \ No newline at end of file