Niels de Vos bec6090996 build: move e2e dependencies into e2e/go.mod
Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
2025-03-07 16:05:04 +00:00

202 lines
5.2 KiB
Go

// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Utility methods to calculate percentiles.
package summary
import (
"fmt"
"math"
"sort"
info "github.com/google/cadvisor/info/v2"
)
const secondsToMilliSeconds = 1000
const milliSecondsToNanoSeconds = 1000000
const secondsToNanoSeconds = secondsToMilliSeconds * milliSecondsToNanoSeconds
type Uint64Slice []uint64
func (s Uint64Slice) Len() int { return len(s) }
func (s Uint64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s Uint64Slice) Less(i, j int) bool { return s[i] < s[j] }
// Get percentile of the provided samples. Round to integer.
func (s Uint64Slice) GetPercentile(d float64) uint64 {
if d < 0.0 || d > 1.0 {
return 0
}
count := s.Len()
if count == 0 {
return 0
}
sort.Sort(s)
n := float64(d * (float64(count) + 1))
idx, frac := math.Modf(n)
index := int(idx)
percentile := float64(s[index-1])
if index > 1 && index < count {
percentile += frac * float64(s[index]-s[index-1])
}
return uint64(percentile)
}
type mean struct {
// current count.
count uint64
// current mean.
Mean float64
}
func (m *mean) Add(value uint64) {
m.count++
if m.count == 1 {
m.Mean = float64(value)
return
}
c := float64(m.count)
v := float64(value)
m.Mean = (m.Mean*(c-1) + v) / c
}
type Percentile interface {
Add(info.Percentiles)
AddSample(uint64)
GetAllPercentiles() info.Percentiles
}
type resource struct {
// list of samples being tracked.
samples Uint64Slice
// average from existing samples.
mean mean
// maximum value seen so far in the added samples.
max uint64
}
// Adds a new percentile sample.
func (r *resource) Add(p info.Percentiles) {
if !p.Present {
return
}
if p.Max > r.max {
r.max = p.Max
}
r.mean.Add(p.Mean)
// Selecting 90p of 90p :(
r.samples = append(r.samples, p.Ninety)
}
// Add a single sample. Internally, we convert it to a fake percentile sample.
func (r *resource) AddSample(val uint64) {
sample := info.Percentiles{
Present: true,
Mean: val,
Max: val,
Fifty: val,
Ninety: val,
NinetyFive: val,
}
r.Add(sample)
}
// Get max, average, and 90p from existing samples.
func (r *resource) GetAllPercentiles() info.Percentiles {
p := info.Percentiles{}
p.Mean = uint64(r.mean.Mean)
p.Max = r.max
p.Fifty = r.samples.GetPercentile(0.5)
p.Ninety = r.samples.GetPercentile(0.9)
p.NinetyFive = r.samples.GetPercentile(0.95)
p.Present = true
return p
}
func NewResource(size int) Percentile {
return &resource{
samples: make(Uint64Slice, 0, size),
mean: mean{count: 0, Mean: 0},
}
}
// Return aggregated percentiles from the provided percentile samples.
func GetDerivedPercentiles(stats []*info.Usage) info.Usage {
cpu := NewResource(len(stats))
memory := NewResource(len(stats))
for _, stat := range stats {
cpu.Add(stat.Cpu)
memory.Add(stat.Memory)
}
usage := info.Usage{}
usage.Cpu = cpu.GetAllPercentiles()
usage.Memory = memory.GetAllPercentiles()
return usage
}
// Calculate part of a minute this sample set represent.
func getPercentComplete(stats []*secondSample) (percent int32) {
numSamples := len(stats)
if numSamples > 1 {
percent = 100
timeRange := stats[numSamples-1].Timestamp.Sub(stats[0].Timestamp).Nanoseconds()
// allow some slack
if timeRange < 58*secondsToNanoSeconds {
percent = int32((timeRange * 100) / 60 * secondsToNanoSeconds)
}
}
return
}
// Calculate cpurate from two consecutive total cpu usage samples.
func getCPURate(latest, previous secondSample) (uint64, error) {
elapsed := latest.Timestamp.Sub(previous.Timestamp).Nanoseconds()
if elapsed < 10*milliSecondsToNanoSeconds {
return 0, fmt.Errorf("elapsed time too small: %d ns: time now %s last %s", elapsed, latest.Timestamp.String(), previous.Timestamp.String())
}
if latest.Cpu < previous.Cpu {
return 0, fmt.Errorf("bad sample: cumulative cpu usage dropped from %d to %d", latest.Cpu, previous.Cpu)
}
// Cpurate is calculated in cpu-milliseconds per second.
cpuRate := (latest.Cpu - previous.Cpu) * secondsToMilliSeconds / uint64(elapsed)
return cpuRate, nil
}
// Returns a percentile sample for a minute by aggregating seconds samples.
func GetMinutePercentiles(stats []*secondSample) info.Usage {
lastSample := secondSample{}
cpu := NewResource(len(stats))
memory := NewResource(len(stats))
for _, stat := range stats {
if !lastSample.Timestamp.IsZero() {
cpuRate, err := getCPURate(*stat, lastSample)
if err != nil {
continue
}
cpu.AddSample(cpuRate)
memory.AddSample(stat.Memory)
} else {
memory.AddSample(stat.Memory)
}
lastSample = *stat
}
percent := getPercentComplete(stats)
return info.Usage{
PercentComplete: percent,
Cpu: cpu.GetAllPercentiles(),
Memory: memory.GetAllPercentiles(),
}
}