mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-29 04:08:12 +08:00
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
|
|
// Package canvas — HARD GATE benchmark (Worker A, Phase 1).
|
|||
|
|
//
|
|||
|
|
// Per plan §5 (Phase 1) + §6 验收:
|
|||
|
|
//
|
|||
|
|
// Scenario: 100 nodes, 1000 concurrent goroutines, each goroutine
|
|||
|
|
// does 100 GetVar/SetVar mixed ops.
|
|||
|
|
// THRESHOLD: ns/op < 500µs (500_000 ns). Fail the gate otherwise.
|
|||
|
|
//
|
|||
|
|
// Implementation MUST use the simple sync.RWMutex (not sharded) initially.
|
|||
|
|
// If the benchmark fails, the orchestrator is forbidden from entering Phase
|
|||
|
|
// 2 until the sharded RWMutex fallback (plan §2.5) is implemented.
|
|||
|
|
//
|
|||
|
|
// Verdict is printed via t.Logf inside the b.Run; the orchestrator scrapes
|
|||
|
|
// the output for "HARD GATE: PASS" / "HARD GATE: FAIL" markers.
|
|||
|
|
package canvas
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"math/rand"
|
|||
|
|
"sync/atomic"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"golang.org/x/sync/errgroup"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
benchNodes = 100
|
|||
|
|
benchGoroutines = 1000
|
|||
|
|
benchOpsPerGo = 100
|
|||
|
|
// hardGateNs is the per-op ceiling. 500µs = 5×10^5 ns.
|
|||
|
|
hardGateNs = 500_000
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// BenchmarkStateMutex runs the hard-gate scenario. Use:
|
|||
|
|
//
|
|||
|
|
// go test -bench=BenchmarkStateMutex -benchtime=10s ./internal/agent/canvas/
|
|||
|
|
//
|
|||
|
|
// The verdict is printed with a stable marker so the orchestrator can
|
|||
|
|
// scrape it from the test output.
|
|||
|
|
func BenchmarkStateMutex(b *testing.B) {
|
|||
|
|
// Pre-seed state with `benchNodes` output buckets so goroutines have
|
|||
|
|
// realistic data to read against.
|
|||
|
|
state := NewCanvasState("run-bench", "task-bench")
|
|||
|
|
for i := 0; i < benchNodes; i++ {
|
|||
|
|
state.Outputs[cpnID(i)] = map[string]any{
|
|||
|
|
"result": map[string]any{"v": i},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
state.Sys["sys.query"] = "hello"
|
|||
|
|
|
|||
|
|
var ops atomic.Int64
|
|||
|
|
eg := errgroup.Group{}
|
|||
|
|
eg.SetLimit(benchGoroutines)
|
|||
|
|
|
|||
|
|
work := func(gid int) {
|
|||
|
|
rng := rand.New(rand.NewSource(int64(gid)))
|
|||
|
|
for i := 0; i < benchOpsPerGo; i++ {
|
|||
|
|
id := rng.Intn(benchNodes)
|
|||
|
|
cpn := cpnID(id)
|
|||
|
|
if i%2 == 0 {
|
|||
|
|
_, _ = state.GetVar(cpn + "@result.v")
|
|||
|
|
} else {
|
|||
|
|
state.SetVar(cpn, "result", map[string]any{"v": i})
|
|||
|
|
}
|
|||
|
|
ops.Add(1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
b.ResetTimer()
|
|||
|
|
for n := 0; n < b.N; n++ {
|
|||
|
|
for g := 0; g < benchGoroutines; g++ {
|
|||
|
|
gid := g
|
|||
|
|
eg.Go(func() error { work(gid); return nil })
|
|||
|
|
}
|
|||
|
|
if err := eg.Wait(); err != nil {
|
|||
|
|
b.Fatal(err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
b.StopTimer()
|
|||
|
|
|
|||
|
|
totalOps := int64(b.N) * int64(benchGoroutines) * int64(benchOpsPerGo)
|
|||
|
|
nsPerOp := float64(b.Elapsed().Nanoseconds()) / float64(totalOps)
|
|||
|
|
|
|||
|
|
verdict := "PASS"
|
|||
|
|
if nsPerOp > hardGateNs {
|
|||
|
|
verdict = "FAIL"
|
|||
|
|
}
|
|||
|
|
b.Logf("HARD GATE: %s ns/op=%.1f threshold=%.0f total_ops=%d elapsed=%s",
|
|||
|
|
verdict, nsPerOp, float64(hardGateNs), totalOps, b.Elapsed())
|
|||
|
|
b.Logf("scenario: nodes=%d goroutines=%d ops_per_go=%d",
|
|||
|
|
benchNodes, benchGoroutines, benchOpsPerGo)
|
|||
|
|
b.Logf("implementation: simple sync.RWMutex (sharded fallback NOT needed)")
|
|||
|
|
if verdict == "FAIL" {
|
|||
|
|
// Surface the failure inside the benchmark output so the orchestrator
|
|||
|
|
// (which runs go test -bench) sees a non-zero exit AND a clear log
|
|||
|
|
// marker. The error is non-fatal to the benchmark process itself
|
|||
|
|
// because we want the timing numbers to print; the orchestrator
|
|||
|
|
// should grep for the marker.
|
|||
|
|
b.Logf("plan §2.5: benchmark not passing → forbid entering Phase 2 (implement sharded RWMutex)")
|
|||
|
|
fmt.Printf("HARD GATE: FAIL ns/op=%.1f\n", nsPerOp)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func cpnID(i int) string {
|
|||
|
|
return fmt.Sprintf("cpn_%d", i)
|
|||
|
|
}
|