Remove sampling from libcontainer CPU stats.

Sampling should be done by higher layer in configurable intervals. This
change removes the percentage stat as sampling is required for this.

Docker-DCO-1.1-Signed-off-by: Victor Marmol <vmarmol@google.com> (github: vmarmol)
This commit is contained in:
Victor Marmol 2014-08-30 09:58:27 -07:00
parent 55430d0db7
commit 2636848328
2 changed files with 44 additions and 96 deletions

View File

@ -1,26 +1,22 @@
package fs package fs
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/system" "github.com/docker/libcontainer/system"
) )
var ( const (
cpuCount = uint64(runtime.NumCPU()) cgroupCpuacctStat = "cpuacct.stat"
clockTicks = uint64(system.GetClockTicks()) nanosecondsInSecond = 1000000000
) )
const nanosecondsInSecond = 1000000000 var clockTicks = uint64(system.GetClockTicks())
type CpuacctGroup struct { type CpuacctGroup struct {
} }
@ -39,103 +35,53 @@ func (s *CpuacctGroup) Remove(d *data) error {
} }
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
var ( userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
err error if err != nil {
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64
)
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
return err
}
startCpu = kernelModeUsage + userModeUsage
if startSystem, err = getSystemCpuUsage(); err != nil {
return err
}
startUsageTime := time.Now()
if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
return err
}
// sample for 100ms
time.Sleep(1000 * time.Millisecond)
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
return err
}
lastCpu = kernelModeUsage + userModeUsage
if lastSystem, err = getSystemCpuUsage(); err != nil {
return err
}
usageSampleDuration := time.Since(startUsageTime)
if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
return err return err
} }
var ( totalUsage, err := getCgroupParamInt(path, "cpuacct.usage")
deltaProc = lastCpu - startCpu if err != nil {
deltaSystem = lastSystem - startSystem return err
deltaUsage = lastUsage - startUsage
)
if deltaSystem > 0.0 {
percentage = uint64((float64(deltaProc) / float64(deltaSystem)) * float64(clockTicks*cpuCount))
} }
// NOTE: a percentage over 100% is valid for POSIX because that means the
// processes is using multiple cores
stats.CpuStats.CpuUsage.PercentUsage = percentage
// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
percpuUsage, err := getPercpuUsage(path) percpuUsage, err := getPercpuUsage(path)
if err != nil { if err != nil {
return err return err
} }
stats.CpuStats.CpuUsage.TotalUsage = lastUsage
stats.CpuStats.CpuUsage.TotalUsage = totalUsage
stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
stats.CpuStats.CpuUsage.UsageInKernelmode = (kernelModeUsage * nanosecondsInSecond) / clockTicks stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
stats.CpuStats.CpuUsage.UsageInUsermode = (userModeUsage * nanosecondsInSecond) / clockTicks stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
return nil return nil
} }
// TODO(vmarmol): Use cgroups stats. // Returns user and kernel usage breakdown in nanoseconds.
func getSystemCpuUsage() (uint64, error) { func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
parts := strings.Fields(sc.Text())
switch parts[0] {
case "cpu":
if len(parts) < 8 {
return 0, fmt.Errorf("invalid number of cpu fields")
}
var total uint64
for _, i := range parts[1:8] {
v, err := strconv.ParseUint(i, 10, 64)
if err != nil {
return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
}
total += v
}
return total, nil
default:
continue
}
}
return 0, fmt.Errorf("invalid stat format")
}
func getCpuUsage(path string) (uint64, uint64, error) {
kernelModeUsage := uint64(0)
userModeUsage := uint64(0) userModeUsage := uint64(0)
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.stat")) kernelModeUsage := uint64(0)
const (
userField = "user"
systemField = "system"
)
// Expected format:
// user <usage in ticks>
// system <usage in ticks>
data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat))
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
} }
fields := strings.Fields(string(data)) fields := strings.Fields(string(data))
if len(fields) != 4 { if len(fields) != 4 {
return 0, 0, fmt.Errorf("Failure - %s is expected to have 4 fields", filepath.Join(path, "cpuacct.stat")) return 0, 0, fmt.Errorf("failure - %s is expected to have 4 fields", filepath.Join(path, cgroupCpuacctStat))
}
if fields[0] != userField {
return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[0], cgroupCpuacctStat, userField)
}
if fields[2] != systemField {
return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[2], cgroupCpuacctStat, systemField)
} }
if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil { if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
return 0, 0, err return 0, 0, err
@ -144,7 +90,7 @@ func getCpuUsage(path string) (uint64, uint64, error) {
return 0, 0, err return 0, 0, err
} }
return kernelModeUsage, userModeUsage, nil return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
} }
func getPercpuUsage(path string) ([]uint64, error) { func getPercpuUsage(path string) ([]uint64, error) {

View File

@ -9,17 +9,19 @@ type ThrottlingData struct {
ThrottledTime uint64 `json:"throttled_time,omitempty"` ThrottledTime uint64 `json:"throttled_time,omitempty"`
} }
// All CPU stats are aggregate since container inception.
type CpuUsage struct { type CpuUsage struct {
// percentage of available CPUs currently being used. // Total CPU time consumed.
PercentUsage uint64 `json:"percent_usage,omitempty"` // Units: nanoseconds.
// nanoseconds of cpu time consumed over the last 100 ms.
CurrentUsage uint64 `json:"current_usage,omitempty"`
// total nanoseconds of cpu time consumed
TotalUsage uint64 `json:"total_usage,omitempty"` TotalUsage uint64 `json:"total_usage,omitempty"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage,omitempty"` PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode. Units: nanoseconds. // Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"` UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode. Units: nanoseconds. // Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"` UsageInUsermode uint64 `json:"usage_in_usermode"`
} }