fix(stats): key generation failures by source and bound the key count

This commit is contained in:
aykhans committed 2026-09-27 01:03:04 +04:00
1 parent c32949c8e0
commit 8da05bb10c
2 files changed
+31 -4

No files matched your search

+13
View File
@@ -17,6 +17,14 @@ import (
const DefaultResponseDurationAccuracy uint32 = 1
const DefaultResponseColumnMaxWidth = 50
// maxResponseKeys bounds the distinct response keys, so error messages carrying
// per-request text cannot grow the map without limit. It stays above the number of
// HTTP status codes, so real ones are never merged.
const maxResponseKeys = 1000
// otherResponseKey collects everything that arrives after the key limit is reached.
const otherResponseKey = "other"
// Duration wraps time.Duration to provide consistent JSON/YAML marshaling as human-readable strings.
type Duration time.Duration
@@ -95,6 +103,11 @@ func (data *SarinResponseData) Add(responseKey string, responseTime time.Duratio
defer data.Unlock()
response, ok := data.Responses[responseKey]
if !ok && len(data.Responses) >= maxResponseKeys {
responseKey = otherResponseKey
response, ok = data.Responses[responseKey]
}
if !ok {
data.Responses[responseKey] = &Response{
durations: map[time.Duration]uint64{
+18 -4
View File
@@ -1,12 +1,14 @@
package sarin
import (
"errors"
"strconv"
"sync/atomic"
"time"
"github.com/valyala/fasthttp"
"go.aykhans.me/sarin/internal/script"
"go.aykhans.me/sarin/internal/types"
)
const dryRunResponseKey = "dry-run"
@@ -29,6 +31,16 @@ func statusCodeToString(code int) string {
return strconv.Itoa(code)
}
// requestErrorKey keys a generation failure by its source instead of its message.
// A script picks its own URLs and error texts, so the message would mint a new
// stats entry per request. The full message still goes to the runtime log.
func requestErrorKey(err error) string {
if chainErr, ok := errors.AsType[types.ScriptChainError](err); ok {
return chainErr.EngineType + " script[" + strconv.Itoa(chainErr.Index) + "] error"
}
return "request generation error"
}
func (s sarin) Worker(
jobs <-chan struct{},
counter *atomic.Uint64,
@@ -112,7 +124,7 @@ func (s sarin) workerStatsWithDynamic(
clientIndex := nextClientIndex()
bindScriptProxy(clientIndex)
if err := requestGenerator(req); err != nil {
s.responses.Add(err.Error(), 0)
s.responses.Add(requestErrorKey(err), 0)
sendLog(runtimeLogLevelError, err.Error())
counter.Add(1)
continue
@@ -144,8 +156,9 @@ func (s sarin) workerStatsWithStatic(
) {
if err := requestGenerator(req); err != nil {
// Static request generation failed, so record all jobs as errors
errorKey := requestErrorKey(err)
for range jobs {
s.responses.Add(err.Error(), 0)
s.responses.Add(errorKey, 0)
sendLog(runtimeLogLevelError, err.Error())
counter.Add(1)
}
@@ -241,7 +254,7 @@ func (s sarin) workerDryRunStatsWithDynamic(
bindScriptProxy(nextClientIndex())
startTime := time.Now()
if err := requestGenerator(req); err != nil {
s.responses.Add(err.Error(), time.Since(startTime))
s.responses.Add(requestErrorKey(err), time.Since(startTime))
sendLog(runtimeLogLevelError, err.Error())
counter.Add(1)
continue
@@ -260,8 +273,9 @@ func (s sarin) workerDryRunStatsWithStatic(
) {
if err := requestGenerator(req); err != nil {
// Static request generation failed, so record all jobs as errors
errorKey := requestErrorKey(err)
for range jobs {
s.responses.Add(err.Error(), 0)
s.responses.Add(errorKey, 0)
sendLog(runtimeLogLevelError, err.Error())
counter.Add(1)
}