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  }