blob: 17a6b1bfa553ca57436527ff2be9ac931225aa82 [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001package pb
2
3import (
4 "fmt"
5 "math"
6 "time"
7
8 "github.com/VividCortex/ewma"
9)
10
11var speedAddLimit = time.Second / 2
12
13type speed struct {
14 ewma ewma.MovingAverage
15 lastStateId uint64
16 prevValue, startValue int64
17 prevTime, startTime time.Time
18}
19
20func (s *speed) value(state *State) float64 {
21 if s.ewma == nil {
22 s.ewma = ewma.NewMovingAverage()
23 }
24 if state.IsFirst() || state.Id() < s.lastStateId {
25 s.reset(state)
26 return 0
27 }
28 if state.Id() == s.lastStateId {
29 return s.ewma.Value()
30 }
31 if state.IsFinished() {
32 return s.absValue(state)
33 }
34 dur := state.Time().Sub(s.prevTime)
35 if dur < speedAddLimit {
36 return s.ewma.Value()
37 }
38 diff := math.Abs(float64(state.Value() - s.prevValue))
39 lastSpeed := diff / dur.Seconds()
40 s.prevTime = state.Time()
41 s.prevValue = state.Value()
42 s.lastStateId = state.Id()
43 s.ewma.Add(lastSpeed)
44 return s.ewma.Value()
45}
46
47func (s *speed) reset(state *State) {
48 s.lastStateId = state.Id()
49 s.startTime = state.Time()
50 s.prevTime = state.Time()
51 s.startValue = state.Value()
52 s.prevValue = state.Value()
53 s.ewma = ewma.NewMovingAverage()
54}
55
56func (s *speed) absValue(state *State) float64 {
57 if dur := state.Time().Sub(s.startTime); dur > 0 {
58 return float64(state.Value()) / dur.Seconds()
59 }
60 return 0
61}
62
63func getSpeedObj(state *State) (s *speed) {
64 if sObj, ok := state.Get(speedObj).(*speed); ok {
65 return sObj
66 }
67 s = new(speed)
68 state.Set(speedObj, s)
69 return
70}
71
72// ElementSpeed calculates current speed by EWMA
73// Optionally can take one or two string arguments.
74// First string will be used as value for format speed, default is "%s p/s".
75// Second string will be used when speed not available, default is "? p/s"
76// In template use as follows: {{speed .}} or {{speed . "%s per second"}} or {{speed . "%s ps" "..."}
77var ElementSpeed ElementFunc = func(state *State, args ...string) string {
78 sp := getSpeedObj(state).value(state)
79 if sp == 0 {
80 return argsHelper(args).getNotEmptyOr(1, "? p/s")
81 }
82 return fmt.Sprintf(argsHelper(args).getNotEmptyOr(0, "%s p/s"), state.Format(int64(round(sp))))
83}