blob: 3f8a97e5a5a10c8c4c1c69ab3697979d5b92c610 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package power
import (
"context"
"regexp"
"strconv"
"strings"
"chromiumos/tast/common/perf"
"chromiumos/tast/errors"
)
// CPUUsageJiffies stores a snapshot of total (load+idle) and load jiffy
// counters from /proc/stat.
type CPUUsageJiffies struct {
total int64
// load is the sum of user, nice, system, iowait, irq, softirq, steal,
// guest.
load int64
}
// ProcfsCPUMetrics holds the CPU metrics read from procfs.
type ProcfsCPUMetrics struct {
metric perf.Metric
lastJiffies CPUUsageJiffies
}
// Assert that ProcfsCPUMetrics can be used in perf.Timeline.
var _ perf.TimelineDatasource = &ProcfsCPUMetrics{}
// NewProcfsCPUMetrics creates a struct to capture CPU metrics with procfs.
func NewProcfsCPUMetrics() *ProcfsCPUMetrics {
return &ProcfsCPUMetrics{}
}
// Setup creates the metric.
func (c *ProcfsCPUMetrics) Setup(ctx context.Context, prefix string) error {
c.metric = perf.Metric{Name: "cpu_usage", Unit: "ratio", Direction: perf.SmallerIsBetter, Multiple: true}
return nil
}
// Start takes the first snapshot of CPU metrics.
func (c *ProcfsCPUMetrics) Start(ctx context.Context) error {
jiffies, err := readJiffies()
if err != nil {
return errors.Wrap(err, "unable to read CPU usage from /proc/stat")
}
c.lastJiffies = jiffies
return nil
}
// readJiffies reads CPU load and calculates total jiffies from /proc/stat.
// This file contains a line for each CPU (core) and an aggregated value of
// all CPUs. The aggregated value (first line) is used in readJiffies.
// Therefore, a reported CPU load value of 1.0 indicates 100% CPU usage on all
// CPUs.
func readJiffies() (CPUUsageJiffies, error) {
line, err := readFirstLine("/proc/stat")
if err != nil {
return CPUUsageJiffies{}, err
}
// Remove duplicate whitespace.
spaceRegexp := regexp.MustCompile(`\s+`)
p := strings.Split(spaceRegexp.ReplaceAllString(line, " "), " ")
var load int64
var total int64
// Line format (number of columns depends on kernel version):
// cpu_id user nice system idle iowait irq softirq steal guest
for i := 1; i < len(p); i++ {
v, err := strconv.ParseInt(p[i], 10, 64)
if err != nil {
return CPUUsageJiffies{}, errors.Wrapf(err, "unexpected token in /proc/stat: %s", p[i])
}
total += v
if i != 4 {
load += v
}
}
return CPUUsageJiffies{total: total, load: load}, nil
}
// Snapshot takes a snapshot of CPU metrics.
func (c *ProcfsCPUMetrics) Snapshot(ctx context.Context, values *perf.Values) error {
jiffies, err := readJiffies()
if err != nil {
return errors.Wrap(err, "unable to read CPU usage from /proc/stat")
}
used := float64(c.lastJiffies.load - jiffies.load)
total := float64(c.lastJiffies.total - jiffies.total)
values.Append(c.metric, used/total)
c.lastJiffies = jiffies
return nil
}
// Stop does nothing.
func (c *ProcfsCPUMetrics) Stop(ctx context.Context, values *perf.Values) error {
return nil
}