github.com/google/cadvisor@v0.49.1/summary/percentiles.go (about)

     1  // Copyright 2015 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Utility methods to calculate percentiles.
    16  
    17  package summary
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"sort"
    23  
    24  	info "github.com/google/cadvisor/info/v2"
    25  )
    26  
    27  const secondsToMilliSeconds = 1000
    28  const milliSecondsToNanoSeconds = 1000000
    29  const secondsToNanoSeconds = secondsToMilliSeconds * milliSecondsToNanoSeconds
    30  
    31  type Uint64Slice []uint64
    32  
    33  func (s Uint64Slice) Len() int           { return len(s) }
    34  func (s Uint64Slice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    35  func (s Uint64Slice) Less(i, j int) bool { return s[i] < s[j] }
    36  
    37  // Get percentile of the provided samples. Round to integer.
    38  func (s Uint64Slice) GetPercentile(d float64) uint64 {
    39  	if d < 0.0 || d > 1.0 {
    40  		return 0
    41  	}
    42  	count := s.Len()
    43  	if count == 0 {
    44  		return 0
    45  	}
    46  	sort.Sort(s)
    47  	n := float64(d * (float64(count) + 1))
    48  	idx, frac := math.Modf(n)
    49  	index := int(idx)
    50  	percentile := float64(s[index-1])
    51  	if index > 1 && index < count {
    52  		percentile += frac * float64(s[index]-s[index-1])
    53  	}
    54  	return uint64(percentile)
    55  }
    56  
    57  type mean struct {
    58  	// current count.
    59  	count uint64
    60  	// current mean.
    61  	Mean float64
    62  }
    63  
    64  func (m *mean) Add(value uint64) {
    65  	m.count++
    66  	if m.count == 1 {
    67  		m.Mean = float64(value)
    68  		return
    69  	}
    70  	c := float64(m.count)
    71  	v := float64(value)
    72  	m.Mean = (m.Mean*(c-1) + v) / c
    73  }
    74  
    75  type Percentile interface {
    76  	Add(info.Percentiles)
    77  	AddSample(uint64)
    78  	GetAllPercentiles() info.Percentiles
    79  }
    80  
    81  type resource struct {
    82  	// list of samples being tracked.
    83  	samples Uint64Slice
    84  	// average from existing samples.
    85  	mean mean
    86  	// maximum value seen so far in the added samples.
    87  	max uint64
    88  }
    89  
    90  // Adds a new percentile sample.
    91  func (r *resource) Add(p info.Percentiles) {
    92  	if !p.Present {
    93  		return
    94  	}
    95  	if p.Max > r.max {
    96  		r.max = p.Max
    97  	}
    98  	r.mean.Add(p.Mean)
    99  	// Selecting 90p of 90p :(
   100  	r.samples = append(r.samples, p.Ninety)
   101  }
   102  
   103  // Add a single sample. Internally, we convert it to a fake percentile sample.
   104  func (r *resource) AddSample(val uint64) {
   105  	sample := info.Percentiles{
   106  		Present:    true,
   107  		Mean:       val,
   108  		Max:        val,
   109  		Fifty:      val,
   110  		Ninety:     val,
   111  		NinetyFive: val,
   112  	}
   113  	r.Add(sample)
   114  }
   115  
   116  // Get max, average, and 90p from existing samples.
   117  func (r *resource) GetAllPercentiles() info.Percentiles {
   118  	p := info.Percentiles{}
   119  	p.Mean = uint64(r.mean.Mean)
   120  	p.Max = r.max
   121  	p.Fifty = r.samples.GetPercentile(0.5)
   122  	p.Ninety = r.samples.GetPercentile(0.9)
   123  	p.NinetyFive = r.samples.GetPercentile(0.95)
   124  	p.Present = true
   125  	return p
   126  }
   127  
   128  func NewResource(size int) Percentile {
   129  	return &resource{
   130  		samples: make(Uint64Slice, 0, size),
   131  		mean:    mean{count: 0, Mean: 0},
   132  	}
   133  }
   134  
   135  // Return aggregated percentiles from the provided percentile samples.
   136  func GetDerivedPercentiles(stats []*info.Usage) info.Usage {
   137  	cpu := NewResource(len(stats))
   138  	memory := NewResource(len(stats))
   139  	for _, stat := range stats {
   140  		cpu.Add(stat.Cpu)
   141  		memory.Add(stat.Memory)
   142  	}
   143  	usage := info.Usage{}
   144  	usage.Cpu = cpu.GetAllPercentiles()
   145  	usage.Memory = memory.GetAllPercentiles()
   146  	return usage
   147  }
   148  
   149  // Calculate part of a minute this sample set represent.
   150  func getPercentComplete(stats []*secondSample) (percent int32) {
   151  	numSamples := len(stats)
   152  	if numSamples > 1 {
   153  		percent = 100
   154  		timeRange := stats[numSamples-1].Timestamp.Sub(stats[0].Timestamp).Nanoseconds()
   155  		// allow some slack
   156  		if timeRange < 58*secondsToNanoSeconds {
   157  			percent = int32((timeRange * 100) / 60 * secondsToNanoSeconds)
   158  		}
   159  	}
   160  	return
   161  }
   162  
   163  // Calculate cpurate from two consecutive total cpu usage samples.
   164  func getCPURate(latest, previous secondSample) (uint64, error) {
   165  	elapsed := latest.Timestamp.Sub(previous.Timestamp).Nanoseconds()
   166  	if elapsed < 10*milliSecondsToNanoSeconds {
   167  		return 0, fmt.Errorf("elapsed time too small: %d ns: time now %s last %s", elapsed, latest.Timestamp.String(), previous.Timestamp.String())
   168  	}
   169  	if latest.Cpu < previous.Cpu {
   170  		return 0, fmt.Errorf("bad sample: cumulative cpu usage dropped from %d to %d", latest.Cpu, previous.Cpu)
   171  	}
   172  	// Cpurate is calculated in cpu-milliseconds per second.
   173  	cpuRate := (latest.Cpu - previous.Cpu) * secondsToMilliSeconds / uint64(elapsed)
   174  	return cpuRate, nil
   175  }
   176  
   177  // Returns a percentile sample for a minute by aggregating seconds samples.
   178  func GetMinutePercentiles(stats []*secondSample) info.Usage {
   179  	lastSample := secondSample{}
   180  	cpu := NewResource(len(stats))
   181  	memory := NewResource(len(stats))
   182  	for _, stat := range stats {
   183  		if !lastSample.Timestamp.IsZero() {
   184  			cpuRate, err := getCPURate(*stat, lastSample)
   185  			if err != nil {
   186  				continue
   187  			}
   188  			cpu.AddSample(cpuRate)
   189  			memory.AddSample(stat.Memory)
   190  		} else {
   191  			memory.AddSample(stat.Memory)
   192  		}
   193  		lastSample = *stat
   194  	}
   195  	percent := getPercentComplete(stats)
   196  	return info.Usage{
   197  		PercentComplete: percent,
   198  		Cpu:             cpu.GetAllPercentiles(),
   199  		Memory:          memory.GetAllPercentiles(),
   200  	}
   201  }