speed.go 2.0 KB

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