node

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

README

Benchmarking Custom Views Tutorial

This guide shows how to benchmark a custom view.View implementation using FSC's exported benchmark infrastructure from an external repository.

What You Implement

Your view must satisfy two interfaces from FSC:

// your_view.go

type YourView struct{ params YourParams }

func (v *YourView) Call(ctx view.Context) (interface{}, error) {
    // your workload logic (e.g. ZKP proving, signing, validation)
    return result, nil
}

type YourViewFactory struct{}

func (f *YourViewFactory) NewView(in []byte) (view.View, error) {
    v := &YourView{}
    if in != nil {
        json.Unmarshal(in, &v.params)
    }
    return v, nil
}

File Structure

your-repo/
├── your_view.go                        # YourView + YourViewFactory
├── bench/
│   ├── standalone_bench_test.go        # pure view benchmark (no FSC node)
│   ├── node_bench_test.go              # local node benchmarks (API + gRPC)
│   └── remote/
│       ├── server/main.go              # remote server
│       └── client/main.go              # remote client
  • your_view.go : your View and ViewFactory implementations.
  • standalone_bench_test.go : benchmarks that call your view directly, no FSC infrastructure.
  • node_bench_test.go : benchmarks that run your view through a real FSC node (API and gRPC).
  • remote/server/main.go and remote/client/main.go : binaries for running benchmarks across two machines.

Define Your Workload

Wrap your view in a node.Workload to plug into FSC's benchmark runners:

var yourWorkload = node.Workload{
    Name:    "your-view",
    Factory: &YourViewFactory{},
    Params:  &YourParams{Size: 1024},
}

Benchmark Levels

1. Standalone - Pure View Performance

No FSC node, no gRPC. Directly benchmarks Call().

// standalone_bench_test.go

func BenchmarkYourView(b *testing.B) {
    f := &YourViewFactory{}
    input, _ := json.Marshal(&YourParams{Size: 1024})

    b.RunParallel(func(pb *testing.PB) {
        v, _ := f.NewView(input)
        for pb.Next() {
            _, _ = v.Call(nil)
        }
    })
    benchmark.ReportTPS(b)
}
go test -bench=BenchmarkYourView -benchmem -count=5 -cpu=1,2,4,8 ./bench/
2. Local Node - View API and gRPC

Spins up an FSC node in-process using node.GenerateConfig and node.SetupNode.

RunAPIBenchmark produces two sub-benchmarks:

  • f=0 : creates one view instance per goroutine and reuses it. Measures pure view execution cost.
  • f=1 : creates a new view via the factory on every call. Measures view execution + factory overhead.

RunAPIGRPCBenchmark adds the full gRPC stack on top and tests across multiple connection counts.

// node_bench_test.go

func BenchmarkYourViewAPI(b *testing.B) {
    testdataPath := b.TempDir()
    nodeConfPath := path.Join(testdataPath, "fsc", "nodes", "test-node.0")

    require.NoError(b, node.GenerateConfig(testdataPath))

    n, err := node.SetupNode(nodeConfPath, node.NamedFactory{
        Name:    yourWorkload.Name,
        Factory: yourWorkload.Factory,
    })
    require.NoError(b, err)
    defer n.Stop()

    vm, err := viewregistry.GetManager(n)
    require.NoError(b, err)

    node.RunAPIBenchmark(b, vm, yourWorkload)
}

func BenchmarkYourViewAPIGRPC(b *testing.B) {
    testdataPath := b.TempDir()
    nodeConfPath := path.Join(testdataPath, "fsc", "nodes", "test-node.0")
    clientConfPath := path.Join(nodeConfPath, "client-config.yaml")

    require.NoError(b, node.GenerateConfig(testdataPath))

    n, err := node.SetupNode(nodeConfPath, node.NamedFactory{
        Name:    yourWorkload.Name,
        Factory: yourWorkload.Factory,
    })
    require.NoError(b, err)
    defer n.Stop()

    node.RunAPIGRPCBenchmark(b, yourWorkload, clientConfPath, *numConn)
}
go test -bench=BenchmarkYourViewAPI -benchmem -count=5 -cpu=1,2,4,8 ./bench/
go test -bench=BenchmarkYourViewAPIGRPC -benchmem -count=5 -cpu=1,8,16 -numConn=1,2,4 ./bench/
3. Remote - Two Machines

Server - register your factory and start the node:

// remote/server/main.go

func main() {
    testdataPath := "./out/testdata"
    nodeConfPath := path.Join(testdataPath, "fsc", "nodes", "test-node.0")

    if err := node.GenerateConfig(testdataPath); err != nil {
        panic(err)
    }

    n, err := node.SetupNode(nodeConfPath,
        node.NamedFactory{Name: "your-view", Factory: &YourViewFactory{}},
    )
    if err != nil {
        panic(err)
    }

    // wait for signal...
}

Client - populate config and call RunRemoteBenchmarkSuite:

// remote/client/main.go

func main() {
    flag.Parse()
    node.RunRemoteBenchmarkSuite(node.RemoteBenchmarkConfig{
        Workloads:      []node.Workload{yourWorkload},
        ClientConfPath: clientConfPath,
        ConnCounts:     *numConn,
        WorkerCounts:   *numWorker,
        WarmupDur:      *warmupDur,
        BenchTime:      *duration,
        Count:          *count,
        BenchName:      "BenchmarkRemote",
    })
}
Reference

FSC's own remote benchmarks use the same infrastructure:

Deployment

When the server starts, node.GenerateConfig generates all required crypto material and configuration into ./out/testdata, including TLS certificates used by both server and client. This folder must be copied to the client machine before running benchmarks. The client currently expects it at ./out/testdata this path is not yet configurable.

# server machine
go run ./bench/remote/server/

# copy crypto material to client
rsync -av server:/path/to/out/testdata ./out/testdata
vim ./out/testdata/fsc/nodes/test-node.0/client-config.yaml  # update server address

# client machine
go run ./bench/remote/client/ \
  -benchtime=10s -count=5 -cpu=1,8,16,32 -numConn=1,2,4 2>&1 | tee results.txt

Summary

Level What it measures How to run
Standalone Pure Call() go test -bench
API f=0 ViewManager + reused view node.RunAPIBenchmark
API f=1 ViewManager + factory per call node.RunAPIBenchmark
gRPC (local) Full FSC gRPC stack node.RunAPIGRPCBenchmark
gRPC (remote) Full stack + real network node.RunRemoteBenchmarkSuite

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultWorkloads = []Workload{
	{
		Name:    "noop",
		Factory: &views.NoopViewFactory{},
	},
	{
		Name:    "cpu",
		Factory: &views.CPUViewFactory{},
		Params:  &views.CPUParams{N: 200000},
	},
	{
		Name:    "sign",
		Factory: &views.ECDSASignViewFactory{},
		Params:  &views.ECDSASignParams{},
	},
}

DefaultWorkloads defines the standard set of benchmark workloads.

View Source
var HistogramOptions = stats.HistogramOptions{
	NumBuckets:   2495,
	GrowthFactor: .01,
}

HistogramOptions are the default bucket settings for latency histograms used by deadline-driven (remote) benchmarks.

Functions

func CreateClients added in v0.9.0

func CreateClients(tb testing.TB, numConn int, clientConfPath string) ([]*benchmark.ViewClient, func())

CreateClients creates numConn ViewClients using the given client config path. It returns the clients and a cleanup function that closes all connections.

func CreateRemoteClients added in v0.9.0

func CreateRemoteClients(numConn int, clientConfPath string) ([]*benchmark.ViewClient, func())

CreateRemoteClients creates numConn ViewClients for remote benchmarks. Unlike CreateClients (which takes testing.TB), this panics on errors since it runs outside of `go test`.

func GenerateConfig

func GenerateConfig(testdataDir string) error

func MakeGRPCCaller added in v0.9.0

func MakeGRPCCaller(tb testing.TB, cli *benchmark.ViewClient, fid string, input []byte) func(ctx context.Context) error

MakeGRPCCaller creates a caller function that sends a signed gRPC command for the given workload name and input. The signed command is created once and reused across all calls for efficiency.

func MakeRemoteGRPCCaller added in v0.9.0

func MakeRemoteGRPCCaller(cli *benchmark.ViewClient, fid string, input []byte) func(ctx context.Context) error

MakeRemoteGRPCCaller creates a caller function for deadline-driven (remote) benchmarks. Unlike MakeGRPCCaller (which takes testing.TB), this version panics on setup errors since it runs outside of `go test`.

func MarshalWorkloadParams added in v0.9.0

func MarshalWorkloadParams(params any) []byte

MarshalWorkloadParams marshals workload params to JSON. Returns nil if params is nil.

func Percentile added in v0.9.0

func Percentile(p float64, h *stats.Histogram) int64

Percentile computes the given percentile (0.0–1.0) from a histogram.

func PrintHistogram added in v0.9.0

func PrintHistogram(hist *stats.Histogram, duration time.Duration)

PrintHistogram formats and prints benchmark results from a histogram, including TPS and latency percentiles (p5, p95, p99).

Output format matches Go's testing.BenchmarkResult so it can be parsed by standard benchmark tooling.

func RunAPIBenchmark added in v0.9.0

func RunAPIBenchmark(b *testing.B, vm ViewManager, wl Workload)

RunAPIBenchmark runs a direct View API benchmark for a single workload against the given view manager. It creates two sub-benchmarks:

  • f=0: reuses a single view instance per goroutine (measures pure view performance)
  • f=1: creates a new view per invocation (measures view + factory overhead)

func RunAPIGRPCBenchmark added in v0.9.0

func RunAPIGRPCBenchmark(b *testing.B, wl Workload, clientConfPath string, connCounts []int)

RunAPIGRPCBenchmark runs a gRPC View API benchmark for a single workload across multiple connection counts. It handles client creation, warmup, and cleanup.

func RunGRPCBenchmark added in v0.9.0

func RunGRPCBenchmark(b *testing.B, ccs []*benchmark.ViewClient, makeCaller func(cli *benchmark.ViewClient) func(ctx context.Context) error)

RunGRPCBenchmark runs a parallel benchmark over multiple ViewClients using round-robin caller selection. This is the core gRPC benchmark loop used by both local and remote node benchmarks.

func RunRemoteBenchmark added in v0.9.0

func RunRemoteBenchmark(
	ccs []*benchmark.ViewClient,
	makeCaller func(cli *benchmark.ViewClient) func(ctx context.Context) error,
	numWorker int,
	warmDeadline, endDeadline time.Time,
) *stats.Histogram

RunRemoteBenchmark runs a deadline-driven benchmark across multiple ViewClients. Unlike RunGRPCBenchmark (which uses testing.B), this uses explicit warmup and measurement phases with histogram-based latency collection.

It returns the merged histogram of all workers. Callers typically pass the result to PrintHistogram().

func RunRemoteBenchmarkSuite added in v0.9.0

func RunRemoteBenchmarkSuite(cfg RemoteBenchmarkConfig)

RunRemoteBenchmarkSuite runs the full remote benchmark loop: workloads × connection counts × worker counts × count.

func SetupClient

func SetupClient(confPath string) (*benchmark.ViewClient, func(), error)

func SetupNode

func SetupNode(confPath string, factories ...NamedFactory) (*node.Node, error)

func WarmupClients

func WarmupClients(ccs []*benchmark.ViewClient, makeCaller func(*benchmark.ViewClient) func(ctx context.Context) error) error

Types

type NamedFactory

type NamedFactory struct {
	Name    string
	Factory viewregistry.Factory
}

type RemoteBenchmarkConfig added in v0.9.0

type RemoteBenchmarkConfig struct {
	Workloads      []Workload
	ClientConfPath string
	ConnCounts     []int // number of gRPC connections to test
	WorkerCounts   []int // number of concurrent workers to test
	WarmupDur      time.Duration
	BenchTime      time.Duration
	Count          int    // number of executions per configuration
	BenchName      string // prefix for output lines, e.g. "BenchmarkAPIGRPCRemote"
}

RemoteBenchmarkConfig holds all parameters for a remote benchmark suite.

type ViewManager added in v0.9.0

type ViewManager interface {
	NewView(id string, in []byte) (view.View, error)
	InitiateView(view view.View, ctx context.Context) (interface{}, error)
}

ViewManager is the minimal interface needed for API benchmarks. It is satisfied by the value returned from viewregistry.GetManager().

type Workload added in v0.9.0

type Workload struct {
	Name    string
	Factory viewregistry.Factory
	Params  any
}

Workload defines a benchmark workload: a named view factory with optional parameters.

Directories

Path Synopsis
remote
client command
server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL