Skip to content

Commit ed38123

Browse files
committed
Add instant stat to derived result.
Before this change, we do not return anything meaningful till cadvisor has been up for a minute. Also, having instant usage in the same stat gives a more complete picture.
1 parent 28984d7 commit ed38123

File tree

3 files changed

+56
-11
lines changed

3 files changed

+56
-11
lines changed

info/container.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,19 @@ type Usage struct {
464464
Memory Percentiles `json:"memory"`
465465
}
466466

467+
// latest sample collected for a container.
468+
type InstantUsage struct {
469+
// cpu rate in cpu milliseconds/second.
470+
Cpu uint64 `json:"cpu"`
471+
// Memory usage in bytes.
472+
Memory uint64 `json:"memory"`
473+
}
474+
467475
type DerivedStats struct {
468476
// Time of generation of these stats.
469477
Timestamp time.Time `json:"timestamp"`
478+
// Latest instantaneous sample.
479+
LatestUsage InstantUsage `json:"latest_usage"`
470480
// Percentiles in last observed minute.
471481
MinuteUsage Usage `json:"minute_usage"`
472482
// Percentile in last hour.

summary/percentiles.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package summary
1818

1919
import (
20+
"fmt"
2021
"math"
2122
"sort"
2223

@@ -147,23 +148,31 @@ func getPercentComplete(stats []*secondSample) (percent int32) {
147148
return
148149
}
149150

151+
// Calculate cpurate from two consecutive total cpu usage samples.
152+
func getCpuRate(latest, previous secondSample) (uint64, error) {
153+
var elapsed int64
154+
elapsed = latest.Timestamp.Sub(previous.Timestamp).Nanoseconds()
155+
if elapsed < 10*milliSecondsToNanoSeconds {
156+
return 0, fmt.Errorf("elapsed time too small: %d ns: time now %s last %s", elapsed, latest.Timestamp.String(), previous.Timestamp.String())
157+
}
158+
if latest.Cpu < previous.Cpu {
159+
return 0, fmt.Errorf("bad sample: cumulative cpu usage dropped from %d to %d", latest.Cpu, previous.Cpu)
160+
}
161+
// Cpurate is calculated in cpu-milliseconds per second.
162+
cpuRate := (latest.Cpu - previous.Cpu) * secondsToMilliSeconds / uint64(elapsed)
163+
return cpuRate, nil
164+
}
165+
150166
// Returns a percentile sample for a minute by aggregating seconds samples.
151167
func GetMinutePercentiles(stats []*secondSample) info.Usage {
152168
lastSample := secondSample{}
153169
cpu := NewResource(len(stats))
154170
memory := NewResource(len(stats))
155171
for _, stat := range stats {
156-
var elapsed int64
157172
if !lastSample.Timestamp.IsZero() {
158-
elapsed = stat.Timestamp.Sub(lastSample.Timestamp).Nanoseconds()
159-
if elapsed < 10*milliSecondsToNanoSeconds {
160-
glog.Infof("Elapsed time too small: %d ns: time now %s last %s", elapsed, stat.Timestamp.String(), lastSample.Timestamp.String())
161-
continue
162-
}
163-
glog.V(2).Infof("Read sample: cpu %d, memory %d", stat.Cpu, memory)
164-
cpuRate := (stat.Cpu - lastSample.Cpu) * secondsToMilliSeconds / uint64(elapsed)
165-
if cpuRate < 0 {
166-
glog.Infof("cpu rate too small: %f ns", cpuRate)
173+
cpuRate, err := getCpuRate(*stat, lastSample)
174+
if err != nil {
175+
glog.V(2).Infof("Skipping sample, %v", err)
167176
continue
168177
}
169178
glog.V(2).Infof("Adding cpu rate sample : %d", cpuRate)

summary/summary.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ type StatsSummary struct {
4747
secondSamples []*secondSample
4848
// minute percentiles. We track 24 * 60 maximum samples.
4949
minuteSamples *SamplesBuffer
50-
// latest derived minute, hour, and day stats. Updated every minute.
50+
// latest derived instant, minute, hour, and day stats. Instant sample updated every second.
51+
// Others updated every minute.
5152
derivedStats info.DerivedStats // Guarded by dataLock.
5253
dataLock sync.RWMutex
5354
}
@@ -65,6 +66,7 @@ func (s *StatsSummary) AddSample(stat info.ContainerStats) error {
6566
sample.Memory = stat.Memory.WorkingSet
6667
}
6768
s.secondSamples = append(s.secondSamples, &sample)
69+
s.updateLatestUsage()
6870
// TODO(jnagal): Use 'available' to avoid unnecessary computation.
6971
if len(s.secondSamples) == 60 {
7072
// Make a minute stat.
@@ -80,6 +82,29 @@ func (s *StatsSummary) AddSample(stat info.ContainerStats) error {
8082
return nil
8183
}
8284

85+
func (s *StatsSummary) updateLatestUsage() {
86+
usage := info.InstantUsage{}
87+
numStats := len(s.secondSamples)
88+
if numStats < 1 {
89+
return
90+
}
91+
latest := s.secondSamples[numStats-1]
92+
usage.Memory = latest.Memory
93+
if numStats > 1 {
94+
previous := s.secondSamples[numStats-2]
95+
cpu, err := getCpuRate(*latest, *previous)
96+
if err == nil {
97+
usage.Cpu = cpu
98+
}
99+
}
100+
101+
s.dataLock.Lock()
102+
defer s.dataLock.Unlock()
103+
s.derivedStats.LatestUsage = usage
104+
s.derivedStats.Timestamp = latest.Timestamp
105+
return
106+
}
107+
83108
// Generate new derived stats based on current minute stats samples.
84109
func (s *StatsSummary) updateDerivedStats() error {
85110
derived := info.DerivedStats{}
@@ -102,6 +127,7 @@ func (s *StatsSummary) updateDerivedStats() error {
102127

103128
s.dataLock.Lock()
104129
defer s.dataLock.Unlock()
130+
derived.LatestUsage = s.derivedStats.LatestUsage
105131
s.derivedStats = derived
106132

107133
return nil

0 commit comments

Comments
 (0)