github.com/XiaoMi/Gaea@v1.2.5/stats/rates.go (about)

     1  /*
     2  Copyright 2017 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package stats
    18  
    19  import (
    20  	"encoding/json"
    21  	"math"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  var timeNow = time.Now
    27  
    28  // CountTracker defines the interface that needs to
    29  // be supported by a variable for being tracked by
    30  // Rates.
    31  type CountTracker interface {
    32  	// Counts returns a map which maps each category to a count.
    33  	// Subsequent calls must return a monotonously increasing count for the same
    34  	// category.
    35  	// Optionally, an implementation may include the "All" category which has
    36  	// the total count across all categories (e.g. timing.go does this).
    37  	Counts() map[string]int64
    38  }
    39  
    40  // wrappedCountTracker implements the CountTracker interface.
    41  // It is used in multidimensional.go to publish specific, one-dimensional
    42  // counters.
    43  type wrappedCountTracker struct {
    44  	f func() map[string]int64
    45  }
    46  
    47  func (t wrappedCountTracker) Counts() map[string]int64 { return t.f() }
    48  
    49  // Rates is capable of reporting the rate (typically QPS)
    50  // for any variable that satisfies the CountTracker interface.
    51  type Rates struct {
    52  	// mu guards all fields.
    53  	mu           sync.Mutex
    54  	timeStamps   *RingInt64
    55  	counts       map[string]*RingInt64
    56  	countTracker CountTracker
    57  	samples      int
    58  	interval     time.Duration
    59  	// previousTotalCount is the total number of counts (across all categories)
    60  	// seen in the last sampling interval.
    61  	// It's used to calculate the latest total rate.
    62  	previousTotalCount int64
    63  	// timestampLastSampling is the time the periodic sampling was run last.
    64  	timestampLastSampling time.Time
    65  	// totalRate is the rate of total counts per second seen in the latest
    66  	// sampling interval e.g. 100 queries / 5 seconds sampling interval = 20 QPS.
    67  	totalRate float64
    68  }
    69  
    70  // NewRates reports rolling rate information for countTracker. samples specifies
    71  // the number of samples to report, and interval specifies the time interval
    72  // between samples. The minimum interval is 1 second.
    73  // If passing the special value of -1s as interval, we don't snapshot.
    74  // (use this for tests).
    75  func NewRates(name string, countTracker CountTracker, samples int, interval time.Duration) *Rates {
    76  	if interval < 1*time.Second && interval != -1*time.Second {
    77  		panic("interval too small")
    78  	}
    79  	rt := &Rates{
    80  		timeStamps:            NewRingInt64(samples + 1),
    81  		counts:                make(map[string]*RingInt64),
    82  		countTracker:          countTracker,
    83  		samples:               samples + 1,
    84  		interval:              interval,
    85  		timestampLastSampling: timeNow(),
    86  	}
    87  	if name != "" {
    88  		publish(name, rt)
    89  	}
    90  	if interval > 0 {
    91  		go rt.track()
    92  	}
    93  	return rt
    94  }
    95  
    96  func (rt *Rates) track() {
    97  	for {
    98  		rt.snapshot()
    99  		<-time.After(rt.interval)
   100  	}
   101  }
   102  
   103  func (rt *Rates) snapshot() {
   104  	rt.mu.Lock()
   105  	defer rt.mu.Unlock()
   106  
   107  	now := timeNow()
   108  	rt.timeStamps.Add(now.UnixNano())
   109  
   110  	// Record current count for each category.
   111  	var totalCount int64
   112  	for k, v := range rt.countTracker.Counts() {
   113  		if k != "All" {
   114  			// Include call categories except "All" (which is returned by the
   115  			// "Timer.Counts()" implementation) to avoid double counting.
   116  			totalCount += v
   117  		}
   118  		if values, ok := rt.counts[k]; ok {
   119  			values.Add(v)
   120  		} else {
   121  			rt.counts[k] = NewRingInt64(rt.samples)
   122  			rt.counts[k].Add(0)
   123  			rt.counts[k].Add(v)
   124  		}
   125  	}
   126  
   127  	// Calculate current total rate.
   128  	// NOTE: We assume that every category with a non-zero value, which was
   129  	// tracked in "rt.previousTotalCount" in a previous sampling interval, is
   130  	// tracked in the current sampling interval in "totalCount" as well.
   131  	// (I.e. categories and their count must not "disappear" in
   132  	//  "rt.countTracker.Counts()".)
   133  	durationSeconds := now.Sub(rt.timestampLastSampling).Seconds()
   134  	rate := float64(totalCount-rt.previousTotalCount) / durationSeconds
   135  	// Round rate with a precision of 0.1.
   136  	rt.totalRate = math.Floor(rate*10+0.5) / 10
   137  	rt.previousTotalCount = totalCount
   138  	rt.timestampLastSampling = now
   139  }
   140  
   141  // Get returns for each category (string) its latest rates (up to X values
   142  // where X is the configured number of samples of the Rates struct).
   143  // Rates are ordered from least recent (index 0) to most recent (end of slice).
   144  func (rt *Rates) Get() (rateMap map[string][]float64) {
   145  	rt.mu.Lock()
   146  	defer rt.mu.Unlock()
   147  
   148  	rateMap = make(map[string][]float64)
   149  	timeStamps := rt.timeStamps.Values()
   150  	if len(timeStamps) <= 1 {
   151  		return
   152  	}
   153  	for k, v := range rt.counts {
   154  		rateMap[k] = make([]float64, len(timeStamps)-1)
   155  		values := v.Values()
   156  		valueIndex := len(values) - 1
   157  		for i := len(timeStamps) - 1; i > 0; i-- {
   158  			if valueIndex <= 0 {
   159  				rateMap[k][i-1] = 0
   160  				continue
   161  			}
   162  			elapsed := float64((timeStamps[i] - timeStamps[i-1]) / 1e9)
   163  			rateMap[k][i-1] = float64(values[valueIndex]-values[valueIndex-1]) / elapsed
   164  			valueIndex--
   165  		}
   166  	}
   167  	return
   168  }
   169  
   170  // GetCount get direct count without devided by duration
   171  func (rt *Rates) GetCount() (rateMap map[string][]int64) {
   172  	rt.mu.Lock()
   173  	defer rt.mu.Unlock()
   174  
   175  	rateMap = make(map[string][]int64)
   176  	timeStamps := rt.timeStamps.Values()
   177  	if len(timeStamps) <= 1 {
   178  		return
   179  	}
   180  	for k, v := range rt.counts {
   181  		rateMap[k] = make([]int64, len(timeStamps)-1)
   182  		values := v.Values()
   183  		valueIndex := len(values) - 1
   184  		for i := len(timeStamps) - 1; i > 0; i-- {
   185  			if valueIndex <= 0 {
   186  				rateMap[k][i-1] = 0
   187  				continue
   188  			}
   189  			rateMap[k][i-1] = values[valueIndex] - values[valueIndex-1]
   190  			valueIndex--
   191  		}
   192  	}
   193  	return
   194  }
   195  
   196  // TotalRate returns the current total rate (counted across categories).
   197  func (rt *Rates) TotalRate() float64 {
   198  	rt.mu.Lock()
   199  	defer rt.mu.Unlock()
   200  
   201  	return rt.totalRate
   202  }
   203  
   204  func (rt *Rates) String() string {
   205  	data, err := json.Marshal(rt.Get())
   206  	if err != nil {
   207  		data, _ = json.Marshal(err.Error())
   208  	}
   209  	return string(data)
   210  }