github.com/nsqio/nsq@v1.3.0/internal/quantile/quantile.go (about) 1 package quantile 2 3 import ( 4 "strings" 5 "sync" 6 "time" 7 8 "github.com/bmizerany/perks/quantile" 9 "github.com/nsqio/nsq/internal/stringy" 10 ) 11 12 type Result struct { 13 Count int `json:"count"` 14 Percentiles []map[string]float64 `json:"percentiles"` 15 } 16 17 func (r *Result) String() string { 18 var s []string 19 for _, item := range r.Percentiles { 20 s = append(s, stringy.NanoSecondToHuman(item["value"])) 21 } 22 return strings.Join(s, ", ") 23 } 24 25 type Quantile struct { 26 sync.Mutex 27 streams [2]quantile.Stream 28 currentIndex uint8 29 lastMoveWindow time.Time 30 currentStream *quantile.Stream 31 32 Percentiles []float64 33 MoveWindowTime time.Duration 34 } 35 36 func New(WindowTime time.Duration, Percentiles []float64) *Quantile { 37 q := Quantile{ 38 currentIndex: 0, 39 lastMoveWindow: time.Now(), 40 MoveWindowTime: WindowTime / 2, 41 Percentiles: Percentiles, 42 } 43 for i := 0; i < 2; i++ { 44 q.streams[i] = *quantile.NewTargeted(Percentiles...) 45 } 46 q.currentStream = &q.streams[0] 47 return &q 48 } 49 50 func (q *Quantile) Result() *Result { 51 if q == nil { 52 return &Result{} 53 } 54 queryHandler := q.QueryHandler() 55 result := Result{ 56 Count: queryHandler.Count(), 57 Percentiles: make([]map[string]float64, len(q.Percentiles)), 58 } 59 for i, p := range q.Percentiles { 60 value := queryHandler.Query(p) 61 result.Percentiles[i] = map[string]float64{"quantile": p, "value": value} 62 } 63 return &result 64 } 65 66 func (q *Quantile) Insert(msgStartTime int64) { 67 q.Lock() 68 69 now := time.Now() 70 for q.IsDataStale(now) { 71 q.moveWindow() 72 } 73 74 q.currentStream.Insert(float64(now.UnixNano() - msgStartTime)) 75 q.Unlock() 76 } 77 78 func (q *Quantile) QueryHandler() *quantile.Stream { 79 q.Lock() 80 now := time.Now() 81 for q.IsDataStale(now) { 82 q.moveWindow() 83 } 84 85 merged := quantile.NewTargeted(q.Percentiles...) 86 merged.Merge(q.streams[0].Samples()) 87 merged.Merge(q.streams[1].Samples()) 88 q.Unlock() 89 return merged 90 } 91 92 func (q *Quantile) IsDataStale(now time.Time) bool { 93 return now.After(q.lastMoveWindow.Add(q.MoveWindowTime)) 94 } 95 96 func (q *Quantile) Merge(them *Quantile) { 97 q.Lock() 98 them.Lock() 99 iUs := q.currentIndex 100 iThem := them.currentIndex 101 102 q.streams[iUs].Merge(them.streams[iThem].Samples()) 103 104 iUs ^= 0x1 105 iThem ^= 0x1 106 q.streams[iUs].Merge(them.streams[iThem].Samples()) 107 108 if q.lastMoveWindow.Before(them.lastMoveWindow) { 109 q.lastMoveWindow = them.lastMoveWindow 110 } 111 q.Unlock() 112 them.Unlock() 113 } 114 115 func (q *Quantile) moveWindow() { 116 q.currentIndex ^= 0x1 117 q.currentStream = &q.streams[q.currentIndex] 118 q.lastMoveWindow = q.lastMoveWindow.Add(q.MoveWindowTime) 119 q.currentStream.Reset() 120 }