vector.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package costmodel
  2. import (
  3. "math"
  4. "sort"
  5. )
  6. type Vector struct {
  7. Timestamp float64 `json:"timestamp"`
  8. Value float64 `json:"value"`
  9. }
  10. // roundTimestamp rounds the given timestamp to the given precision; e.g. a
  11. // timestamp given in seconds, rounded to precision 10, will be rounded
  12. // to the nearest value dividible by 10 (24 goes to 20, but 25 goes to 30).
  13. func roundTimestamp(ts float64, precision float64) float64 {
  14. return math.Round(ts/precision) * precision
  15. }
  16. // ApplyVectorOp accepts two vectors, synchronizes timestamps, and executes an operation
  17. // on each vector. See VectorJoinOp for details.
  18. func ApplyVectorOp(xvs []*Vector, yvs []*Vector, op VectorJoinOp) []*Vector {
  19. // round all non-zero timestamps to the nearest 10 second mark
  20. for _, yv := range yvs {
  21. if yv.Timestamp != 0 {
  22. yv.Timestamp = roundTimestamp(yv.Timestamp, 10.0)
  23. }
  24. }
  25. for _, xv := range xvs {
  26. if xv.Timestamp != 0 {
  27. xv.Timestamp = roundTimestamp(xv.Timestamp, 10.0)
  28. }
  29. }
  30. // if xvs is empty, return yvs
  31. if xvs == nil || len(xvs) == 0 {
  32. return yvs
  33. }
  34. // if yvs is empty, return xvs
  35. if yvs == nil || len(yvs) == 0 {
  36. return xvs
  37. }
  38. // result contains the final vector slice after joining xvs and yvs
  39. var result []*Vector
  40. // timestamps stores all timestamps present in both vector slices
  41. // without duplicates
  42. var timestamps []float64
  43. // turn each vector slice into a map of timestamp-to-value so that
  44. // values at equal timestamps can be lined-up and summed
  45. xMap := make(map[float64]float64)
  46. for _, xv := range xvs {
  47. if xv.Timestamp == 0 {
  48. continue
  49. }
  50. xMap[xv.Timestamp] = xv.Value
  51. timestamps = append(timestamps, xv.Timestamp)
  52. }
  53. yMap := make(map[float64]float64)
  54. for _, yv := range yvs {
  55. if yv.Timestamp == 0 {
  56. continue
  57. }
  58. yMap[yv.Timestamp] = yv.Value
  59. if _, ok := xMap[yv.Timestamp]; !ok {
  60. // no need to double add, since we'll range over sorted timestamps and check.
  61. timestamps = append(timestamps, yv.Timestamp)
  62. }
  63. }
  64. // iterate over each timestamp to produce a final op vector slice
  65. sort.Float64s(timestamps)
  66. for _, t := range timestamps {
  67. x, okX := xMap[t]
  68. y, okY := yMap[t]
  69. sv := &Vector{Timestamp: t}
  70. if op(sv, VectorValue(x, okX), VectorValue(y, okY)) {
  71. result = append(result, sv)
  72. }
  73. }
  74. return result
  75. }
  76. // VectorJoinOp is an operation func that accepts a result vector pointer
  77. // for a specific timestamp and two float64 pointers representing the
  78. // input vectors for that timestamp. x or y inputs can be nil, but not
  79. // both. The op should use x and y values to set the Value on the result
  80. // ptr. If a result could not be generated, the op should return false,
  81. // which will omit the vector for the specific timestamp. Otherwise,
  82. // return true denoting a successful op.
  83. type VectorJoinOp func(result *Vector, x *float64, y *float64) bool
  84. // returns a nil ptr or valid float ptr based on the ok bool
  85. func VectorValue(v float64, ok bool) *float64 {
  86. if !ok {
  87. return nil
  88. }
  89. return &v
  90. }
  91. // NormalizeVectorByVector produces a version of xvs (a slice of Vectors)
  92. // which has had its timestamps rounded and its values divided by the values
  93. // of the Vectors of yvs, such that yvs is the "unit" Vector slice.
  94. func NormalizeVectorByVector(xvs []*Vector, yvs []*Vector) []*Vector {
  95. normalizeOp := func(result *Vector, x *float64, y *float64) bool {
  96. if x != nil && y != nil && *y != 0 {
  97. result.Value = *x / *y
  98. } else if x != nil {
  99. result.Value = *x
  100. } else if y != nil {
  101. result.Value = 0
  102. }
  103. return true
  104. }
  105. return ApplyVectorOp(xvs, yvs, normalizeOp)
  106. }