* Bump the go-modules-updates group in /backend with 7 updates Bumps the go-modules-updates group in /backend with 7 updates: | Package | From | To | | --- | --- | --- | | [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.21.1` | `2.23.1` | | [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.2.3` | `5.2.4` | | [github.com/go-pkgz/rest](https://github.com/go-pkgz/rest) | `1.20.6` | `1.21.0` | | [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) | `5.3.0` | `5.3.1` | | [golang.org/x/crypto](https://github.com/golang/crypto) | `0.46.0` | `0.47.0` | | [golang.org/x/image](https://github.com/golang/image) | `0.34.0` | `0.35.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.48.0` | `0.49.0` | Updates `github.com/alecthomas/chroma/v2` from 2.21.1 to 2.23.1 - [Release notes](https://github.com/alecthomas/chroma/releases) - [Commits](https://github.com/alecthomas/chroma/compare/v2.21.1...v2.23.1) Updates `github.com/go-chi/chi/v5` from 5.2.3 to 5.2.4 - [Release notes](https://github.com/go-chi/chi/releases) - [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4) Updates `github.com/go-pkgz/rest` from 1.20.6 to 1.21.0 - [Release notes](https://github.com/go-pkgz/rest/releases) - [Commits](https://github.com/go-pkgz/rest/compare/v1.20.6...v1.21.0) Updates `github.com/golang-jwt/jwt/v5` from 5.3.0 to 5.3.1 - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.3.0...v5.3.1) Updates `golang.org/x/crypto` from 0.46.0 to 0.47.0 - [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0) Updates `golang.org/x/image` from 0.34.0 to 0.35.0 - [Commits](https://github.com/golang/image/compare/v0.34.0...v0.35.0) Updates `golang.org/x/net` from 0.48.0 to 0.49.0 - [Commits](https://github.com/golang/net/compare/v0.48.0...v0.49.0) --- updated-dependencies: - dependency-name: github.com/alecthomas/chroma/v2 dependency-version: 2.23.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: github.com/go-chi/chi/v5 dependency-version: 5.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: github.com/go-pkgz/rest dependency-version: 1.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.3.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: golang.org/x/crypto dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/image dependency-version: 0.35.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/net dependency-version: 0.49.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates ... Signed-off-by: dependabot[bot] <support@github.com> * Run go mod tidy in examples directory Co-authored-by: paskal <712534+paskal@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: paskal <712534+paskal@users.noreply.github.com>
169 lines
4.5 KiB
Go
169 lines
4.5 KiB
Go
package rest
|
|
|
|
import (
|
|
"container/list"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var maxTimeRangeDefault = time.Duration(15) * time.Minute
|
|
|
|
// Benchmarks is a basic benchmarking middleware collecting and reporting performance metrics
|
|
// It keeps track of the requests speeds and counts in 1s benchData buckets ,limiting the number of buckets
|
|
// to maxTimeRange. User can request the benchmark for any time duration. This is intended to be used
|
|
// for retrieving the benchmark data for the last minute, 5 minutes and up to maxTimeRange.
|
|
type Benchmarks struct {
|
|
st time.Time
|
|
data *list.List
|
|
lock sync.RWMutex
|
|
maxTimeRange time.Duration
|
|
|
|
nowFn func() time.Time // for testing only
|
|
}
|
|
|
|
type benchData struct {
|
|
// 1s aggregates
|
|
requests int
|
|
respTime time.Duration
|
|
minRespTime time.Duration
|
|
maxRespTime time.Duration
|
|
ts time.Time
|
|
}
|
|
|
|
// BenchmarkStats holds the stats for a given interval
|
|
type BenchmarkStats struct {
|
|
Requests int `json:"requests"`
|
|
RequestsSec float64 `json:"requests_sec"`
|
|
AverageRespTime int64 `json:"average_resp_time"`
|
|
MinRespTime int64 `json:"min_resp_time"`
|
|
MaxRespTime int64 `json:"max_resp_time"`
|
|
}
|
|
|
|
// NewBenchmarks creates a new benchmark middleware
|
|
func NewBenchmarks() *Benchmarks {
|
|
res := &Benchmarks{
|
|
st: time.Now(),
|
|
data: list.New(),
|
|
nowFn: time.Now,
|
|
maxTimeRange: maxTimeRangeDefault,
|
|
}
|
|
return res
|
|
}
|
|
|
|
// WithTimeRange sets the maximum time range for the benchmark to keep data for.
|
|
// Default is 15 minutes. The increase of this range will change memory utilization as each second of the range
|
|
// kept as benchData aggregate. The default means 15*60 = 900 seconds of data aggregate.
|
|
// Larger range allows for longer time periods to be benchmarked.
|
|
func (b *Benchmarks) WithTimeRange(maximum time.Duration) *Benchmarks {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
b.maxTimeRange = maximum
|
|
return b
|
|
}
|
|
|
|
// Handler calculates 1/5/10m request per second and allows to access those values
|
|
func (b *Benchmarks) Handler(next http.Handler) http.Handler {
|
|
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
st := b.nowFn()
|
|
defer func() {
|
|
b.update(time.Since(st))
|
|
}()
|
|
next.ServeHTTP(w, r)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
|
|
func (b *Benchmarks) update(reqDuration time.Duration) {
|
|
now := b.nowFn().Truncate(time.Second)
|
|
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
// keep maxTimeRange in the list, drop the rest
|
|
for e := b.data.Front(); e != nil; e = e.Next() {
|
|
if b.data.Front().Value.(benchData).ts.After(b.nowFn().Add(-b.maxTimeRange)) {
|
|
break
|
|
}
|
|
b.data.Remove(b.data.Front())
|
|
}
|
|
|
|
last := b.data.Back()
|
|
if last == nil || last.Value.(benchData).ts.Before(now) {
|
|
b.data.PushBack(benchData{requests: 1, respTime: reqDuration, ts: now,
|
|
minRespTime: reqDuration, maxRespTime: reqDuration})
|
|
return
|
|
}
|
|
|
|
bd := last.Value.(benchData)
|
|
bd.requests++
|
|
bd.respTime += reqDuration
|
|
|
|
if bd.minRespTime == 0 || reqDuration < bd.minRespTime {
|
|
bd.minRespTime = reqDuration
|
|
}
|
|
if bd.maxRespTime == 0 || reqDuration > bd.maxRespTime {
|
|
bd.maxRespTime = reqDuration
|
|
}
|
|
|
|
last.Value = bd
|
|
}
|
|
|
|
// Stats returns the current benchmark stats for the given duration
|
|
func (b *Benchmarks) Stats(interval time.Duration) BenchmarkStats {
|
|
if interval < time.Second { // minimum interval is 1s due to the bucket size
|
|
return BenchmarkStats{}
|
|
}
|
|
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
|
|
var (
|
|
requests int
|
|
respTime time.Duration
|
|
)
|
|
|
|
now := b.nowFn().Truncate(time.Second)
|
|
cutoff := now.Add(-interval)
|
|
stInterval, fnInterval := time.Time{}, time.Time{}
|
|
var minRespTime, maxRespTime time.Duration
|
|
count := 0
|
|
|
|
for e := b.data.Back(); e != nil && count < int(interval.Seconds()); e = e.Prev() { // reverse order
|
|
bd := e.Value.(benchData)
|
|
if bd.ts.Before(cutoff) {
|
|
break
|
|
}
|
|
|
|
if minRespTime == 0 || bd.minRespTime < minRespTime {
|
|
minRespTime = bd.minRespTime
|
|
}
|
|
if maxRespTime == 0 || bd.maxRespTime > maxRespTime {
|
|
maxRespTime = bd.maxRespTime
|
|
}
|
|
requests += bd.requests
|
|
respTime += bd.respTime
|
|
if fnInterval.IsZero() {
|
|
fnInterval = bd.ts.Add(time.Second)
|
|
}
|
|
stInterval = bd.ts
|
|
count++
|
|
}
|
|
|
|
if requests == 0 {
|
|
return BenchmarkStats{}
|
|
}
|
|
|
|
// ensure we calculate rate based on actual interval
|
|
actualInterval := max(fnInterval.Sub(stInterval), time.Second)
|
|
|
|
return BenchmarkStats{
|
|
Requests: requests,
|
|
RequestsSec: float64(requests) / actualInterval.Seconds(),
|
|
AverageRespTime: respTime.Microseconds() / int64(requests),
|
|
MinRespTime: minRespTime.Microseconds(),
|
|
MaxRespTime: maxRespTime.Microseconds(),
|
|
}
|
|
}
|