{"record":{"id":"bfa06b406aa81f18","repo":"tsenart/vegeta","slug":"timeseries-non-monotonically-increasing-timestamp","errorCode":null,"errorMessage":"timeseries: non monotonically increasing timestamp","messagePattern":"timeseries: non monotonically increasing timestamp","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/plot/timeseries.go","lineNumber":29,"sourceCode":"// An in-memory timeSeries of points with high compression of\n// both timestamps and values.  It's not safe for concurrent use.\ntype timeSeries struct {\n\tattack string\n\tlabel  string\n\tprev   uint64\n\tdata   *tsz.Series\n\tlen    int\n}\n\nfunc newTimeSeries(attack, label string) *timeSeries {\n\treturn &timeSeries{\n\t\tattack: attack,\n\t\tlabel:  label,\n\t\tdata:   tsz.New(0),\n\t}\n}\n\nvar errMonotonicTimestamp = errors.New(\"timeseries: non monotonically increasing timestamp\")\n\nfunc (ts *timeSeries) add(t uint64, v float64) error {\n\tif ts.prev > t {\n\t\treturn errMonotonicTimestamp\n\t}\n\n\tts.data.Push(t, v)\n\tts.prev = t\n\tts.len++\n\n\treturn nil\n}\n\nfunc (ts *timeSeries) iter() lttb.Iter {\n\tit := ts.data.Iter()\n\treturn func(count int) ([]lttb.Point, error) {\n\t\tps := make([]lttb.Point, 0, count)\n\t\tfor i := 0; i < count && it.Next(); i++ {","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/tsenart/vegeta/blob/cf5811269046c672a604b1eb352204d30f16ae4a/lib/plot/timeseries.go#L11-L47","documentation":"errMonotonicTimestamp is returned by timeSeries.add when a sample's timestamp is smaller than the previously recorded one. The plotting time series requires strictly forward-moving timestamps to keep buckets ordered.","triggerScenarios":"Calling add(t, v) on lib/plot's timeSeries with t < ts.prev — e.g. replaying out-of-order results or feeding unsorted result slices into the plotting/reporting path.","commonSituations":"Sorting results by the wrong field, multiple concurrent attackers writing to one time series without ordering, or clock changes producing non-increasing uint64 nanosecond timestamps.","solutions":["Sort results by timestamp before feeding them into the plot/report builder.","Ensure timestamps come from a single monotonic source (e.g. time.Since(start) rather than wall clock).","If merging series, merge in sorted order or skip/drop out-of-order samples."],"exampleFix":"// before\nfor _, r := range results { ts.add(uint64(r.Timestamp.UnixNano()), 1) }\n// after\nsort.Slice(results, func(i, j int) bool { return results[i].Timestamp.Before(results[j].Timestamp) })\nfor _, r := range results { ts.add(uint64(r.Timestamp.UnixNano()), 1) }","handlingStrategy":"validation","validationCode":"sort.Slice(results, func(i, j int) bool {\n    return results[i].Timestamp.Before(results[j].Timestamp)\n})","typeGuard":null,"tryCatchPattern":"if err := ts.add(uint64(r.Timestamp.UnixNano()), v); err != nil {\n    log.Printf(\"skipping out-of-order sample: %v\", err)\n    continue\n}","preventionTips":["Sort results by Timestamp before plotting/reporting","Use monotonic clock deltas instead of wall-clock times","Sequence timestamps when merging multiple attack outputs"],"tags":["timeseries","plotting","ordering"],"backgroundTag":"non-monotonic-timestamp","analyzedSha":"cf5811269046c672a604b1eb352204d30f16ae4a","analyzedAt":"2026-08-31T11:04:20.464Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}