mirror of
https://github.com/aykhans/sarin.git
synced 2026-10-17 22:53:52 +00:00
fix(stats): key generation failures by source and bound the key count
This commit is contained in:
2 files changed
+31
-4
No files matched your search
@@ -17,6 +17,14 @@ import (
|
|||||||
const DefaultResponseDurationAccuracy uint32 = 1
|
const DefaultResponseDurationAccuracy uint32 = 1
|
||||||
const DefaultResponseColumnMaxWidth = 50
|
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.
|
// Duration wraps time.Duration to provide consistent JSON/YAML marshaling as human-readable strings.
|
||||||
type Duration time.Duration
|
type Duration time.Duration
|
||||||
|
|
||||||
@@ -95,6 +103,11 @@ func (data *SarinResponseData) Add(responseKey string, responseTime time.Duratio
|
|||||||
defer data.Unlock()
|
defer data.Unlock()
|
||||||
|
|
||||||
response, ok := data.Responses[responseKey]
|
response, ok := data.Responses[responseKey]
|
||||||
|
if !ok && len(data.Responses) >= maxResponseKeys {
|
||||||
|
responseKey = otherResponseKey
|
||||||
|
response, ok = data.Responses[responseKey]
|
||||||
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
data.Responses[responseKey] = &Response{
|
data.Responses[responseKey] = &Response{
|
||||||
durations: map[time.Duration]uint64{
|
durations: map[time.Duration]uint64{
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package sarin
|
package sarin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"go.aykhans.me/sarin/internal/script"
|
"go.aykhans.me/sarin/internal/script"
|
||||||
|
"go.aykhans.me/sarin/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dryRunResponseKey = "dry-run"
|
const dryRunResponseKey = "dry-run"
|
||||||
@@ -29,6 +31,16 @@ func statusCodeToString(code int) string {
|
|||||||
return strconv.Itoa(code)
|
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(
|
func (s sarin) Worker(
|
||||||
jobs <-chan struct{},
|
jobs <-chan struct{},
|
||||||
counter *atomic.Uint64,
|
counter *atomic.Uint64,
|
||||||
@@ -112,7 +124,7 @@ func (s sarin) workerStatsWithDynamic(
|
|||||||
clientIndex := nextClientIndex()
|
clientIndex := nextClientIndex()
|
||||||
bindScriptProxy(clientIndex)
|
bindScriptProxy(clientIndex)
|
||||||
if err := requestGenerator(req); err != nil {
|
if err := requestGenerator(req); err != nil {
|
||||||
s.responses.Add(err.Error(), 0)
|
s.responses.Add(requestErrorKey(err), 0)
|
||||||
sendLog(runtimeLogLevelError, err.Error())
|
sendLog(runtimeLogLevelError, err.Error())
|
||||||
counter.Add(1)
|
counter.Add(1)
|
||||||
continue
|
continue
|
||||||
@@ -144,8 +156,9 @@ func (s sarin) workerStatsWithStatic(
|
|||||||
) {
|
) {
|
||||||
if err := requestGenerator(req); err != nil {
|
if err := requestGenerator(req); err != nil {
|
||||||
// Static request generation failed, so record all jobs as errors
|
// Static request generation failed, so record all jobs as errors
|
||||||
|
errorKey := requestErrorKey(err)
|
||||||
for range jobs {
|
for range jobs {
|
||||||
s.responses.Add(err.Error(), 0)
|
s.responses.Add(errorKey, 0)
|
||||||
sendLog(runtimeLogLevelError, err.Error())
|
sendLog(runtimeLogLevelError, err.Error())
|
||||||
counter.Add(1)
|
counter.Add(1)
|
||||||
}
|
}
|
||||||
@@ -241,7 +254,7 @@ func (s sarin) workerDryRunStatsWithDynamic(
|
|||||||
bindScriptProxy(nextClientIndex())
|
bindScriptProxy(nextClientIndex())
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
if err := requestGenerator(req); err != nil {
|
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())
|
sendLog(runtimeLogLevelError, err.Error())
|
||||||
counter.Add(1)
|
counter.Add(1)
|
||||||
continue
|
continue
|
||||||
@@ -260,8 +273,9 @@ func (s sarin) workerDryRunStatsWithStatic(
|
|||||||
) {
|
) {
|
||||||
if err := requestGenerator(req); err != nil {
|
if err := requestGenerator(req); err != nil {
|
||||||
// Static request generation failed, so record all jobs as errors
|
// Static request generation failed, so record all jobs as errors
|
||||||
|
errorKey := requestErrorKey(err)
|
||||||
for range jobs {
|
for range jobs {
|
||||||
s.responses.Add(err.Error(), 0)
|
s.responses.Add(errorKey, 0)
|
||||||
sendLog(runtimeLogLevelError, err.Error())
|
sendLog(runtimeLogLevelError, err.Error())
|
||||||
counter.Add(1)
|
counter.Add(1)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user