mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-06-13 15:47:38 +08:00
Avoid rebuilding skipped-node state during wildcard fallback so the matcher no longer loops on the same static branch and stops allocating on the hot path. Add focused route benchmarks and regression coverage to keep the optimized path stable.
130 lines
3 KiB
Go
130 lines
3 KiB
Go
package touka
|
|
|
|
import "testing"
|
|
|
|
var (
|
|
benchmarkRouteHandlers HandlersChain
|
|
benchmarkRouteFullPath string
|
|
benchmarkRouteParamsLen int
|
|
benchmarkRouteCIPath []byte
|
|
benchmarkRouteCIFound bool
|
|
)
|
|
|
|
func buildRouteMatchBenchmarkTree() *node {
|
|
tree := &node{}
|
|
routes := []string{
|
|
"/",
|
|
"/health",
|
|
"/contact",
|
|
"/api/v1/users",
|
|
"/api/v1/users/:id",
|
|
"/api/v1/users/:id/settings",
|
|
"/assets/*filepath",
|
|
"/abc/b",
|
|
"/abc/:p1/cde",
|
|
"/abc/:p1/:p2/def/*filepath",
|
|
}
|
|
|
|
for _, route := range routes {
|
|
tree.addRoute(route, fakeHandler(route))
|
|
}
|
|
|
|
return tree
|
|
}
|
|
|
|
func benchmarkRouteLookup(b *testing.B, tree *node, path string, wantFullPath string) {
|
|
b.Helper()
|
|
|
|
params := make(Params, 0, 4)
|
|
skipped := make([]skippedNode, 0, 8)
|
|
|
|
value := tree.getValue(path, ¶ms, &skipped, true)
|
|
if wantFullPath == "" {
|
|
if value.handlers != nil {
|
|
b.Fatalf("expected no match for %q, got %q", path, value.fullPath)
|
|
}
|
|
} else {
|
|
if value.handlers == nil {
|
|
b.Fatalf("expected match for %q, got nil handlers", path)
|
|
}
|
|
if value.fullPath != wantFullPath {
|
|
b.Fatalf("expected full path %q for %q, got %q", wantFullPath, path, value.fullPath)
|
|
}
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
params = params[:0]
|
|
skipped = skipped[:0]
|
|
value = tree.getValue(path, ¶ms, &skipped, true)
|
|
}
|
|
|
|
benchmarkRouteHandlers = value.handlers
|
|
benchmarkRouteFullPath = value.fullPath
|
|
if value.params != nil {
|
|
benchmarkRouteParamsLen = len(*value.params)
|
|
} else {
|
|
benchmarkRouteParamsLen = 0
|
|
}
|
|
}
|
|
|
|
func BenchmarkRouteMatch(b *testing.B) {
|
|
tree := buildRouteMatchBenchmarkTree()
|
|
|
|
b.Run("StaticHit", func(b *testing.B) {
|
|
benchmarkRouteLookup(b, tree, "/api/v1/users", "/api/v1/users")
|
|
})
|
|
|
|
b.Run("ParamHit", func(b *testing.B) {
|
|
benchmarkRouteLookup(b, tree, "/api/v1/users/123", "/api/v1/users/:id")
|
|
})
|
|
|
|
b.Run("BacktrackingHit", func(b *testing.B) {
|
|
benchmarkRouteLookup(b, tree, "/abc/b/d/def/some/file.txt", "/abc/:p1/:p2/def/*filepath")
|
|
})
|
|
|
|
b.Run("Miss", func(b *testing.B) {
|
|
benchmarkRouteLookup(b, tree, "/does/not/exist", "")
|
|
})
|
|
|
|
b.Run("CaseInsensitiveHit", func(b *testing.B) {
|
|
path := "/API/V1/USERS/123/SETTINGS"
|
|
out, found := tree.findCaseInsensitivePath(path, true)
|
|
if !found {
|
|
b.Fatalf("expected fixed-path match for %q", path)
|
|
}
|
|
if got := string(out); got != "/api/v1/users/123/settings" {
|
|
b.Fatalf("expected fixed-path result %q, got %q", "/api/v1/users/123/settings", got)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
out, found = tree.findCaseInsensitivePath(path, true)
|
|
}
|
|
|
|
benchmarkRouteCIPath = out
|
|
benchmarkRouteCIFound = found
|
|
})
|
|
|
|
b.Run("CaseInsensitiveMiss", func(b *testing.B) {
|
|
path := "/DOES/NOT/EXIST"
|
|
out, found := tree.findCaseInsensitivePath(path, true)
|
|
if found || out != nil {
|
|
b.Fatalf("expected no fixed-path match for %q, got %q, %t", path, string(out), found)
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
out, found = tree.findCaseInsensitivePath(path, true)
|
|
}
|
|
|
|
benchmarkRouteCIPath = out
|
|
benchmarkRouteCIFound = found
|
|
})
|
|
}
|