blob: e65822779daa424258bbb08e4ccbb64a37ee6b7a [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"
"io/ioutil"
"path"
"regexp"
"strconv"
"strings"
"chromiumos/tast/common/perf"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
// ListSysfsThermalSensors lists names and paths of thermal sensors which can be read through sysfs.
func ListSysfsThermalSensors(ctx context.Context) (map[string]string, error) {
// TODO(springerm): Remove ContextLogf()s after checking this function works on all platforms
thermalSensors := make(map[string]string)
const sysfsThermalPath = "/sys/class/thermal"
testing.ContextLog(ctx, "Listing thermal sensors in ", sysfsThermalPath)
files, err := ioutil.ReadDir(sysfsThermalPath)
if err != nil {
return thermalSensors, errors.Wrap(err, "failed to read sysfs dir")
}
// Avoid duplicate metric names by adding a counter suffix if necessary.
typeCounter := make(map[string]int)
for _, file := range files {
if !strings.HasPrefix(file.Name(), "thermal_zone") {
testing.ContextLogf(ctx, "%v is not a thermal sensor", file.Name())
continue
}
devPath := path.Join(sysfsThermalPath, file.Name())
_, err := readInt64(path.Join(devPath, "temp"))
if err != nil {
testing.ContextLogf(ctx, "%v is not readable", devPath)
continue
}
name, err := readFirstLine(path.Join(devPath, "type"))
if err != nil {
testing.ContextLogf(ctx, "%v is not readable", devPath)
continue
}
typeCounter[name] = typeCounter[name] + 1
if typeCounter[name] > 1 {
name = name + "_" + strconv.Itoa(typeCounter[name])
}
thermalSensors[name] = devPath
}
return thermalSensors, nil
}
// ThermalMetric holds the name, sysfs path and perf.Metric object of a thermal sensor.
type ThermalMetric struct {
name string
path string
metric perf.Metric
}
// SysfsThermalMetrics holds the metrics to read from sysfs.
type SysfsThermalMetrics struct {
metrics []ThermalMetric
}
// Assert that SysfsThermalMetrics can be used in perf.Timeline.
var _ perf.TimelineDatasource = &SysfsThermalMetrics{}
// NewSysfsThermalMetrics creates a struct to capture thermal metrics with sysfs.
func NewSysfsThermalMetrics() *SysfsThermalMetrics {
return &SysfsThermalMetrics{}
}
// Setup checks which thermal sensors are available.
func (b *SysfsThermalMetrics) Setup(ctx context.Context, prefix string) error {
b.metrics = []ThermalMetric{}
thermalSensors, err := ListSysfsThermalSensors(ctx)
if err != nil {
return err
}
if len(thermalSensors) == 0 {
testing.ContextLog(ctx, "No thermal metrics found")
return nil
}
testing.ContextLogf(ctx, "SysfsThermalMetrics uses %v sensors:", len(thermalSensors))
for name, path := range thermalSensors {
testing.ContextLogf(ctx, "%s (%s)", name, path)
// Some sensor names contain characters that are not allowed in metric names.
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
metricName := prefix + reg.ReplaceAllString(name, "_")
perfMetric := perf.Metric{Name: metricName, Unit: "deg_C", Direction: perf.SmallerIsBetter, Multiple: true}
thermalMetric := ThermalMetric{name: name, path: path, metric: perfMetric}
b.metrics = append(b.metrics, thermalMetric)
}
return nil
}
// Start is not required for SysfsThermalMetrics.
func (b *SysfsThermalMetrics) Start(ctx context.Context) error {
return nil
}
// SnapshotValues takes a snapshot of thermal metrics and returns a map of metrics to values.
func (b *SysfsThermalMetrics) SnapshotValues(ctx context.Context) (map[perf.Metric]float64, error) {
m := make(map[perf.Metric]float64)
for _, metric := range b.metrics {
tempFile := path.Join(metric.path, "temp")
temp, err := readInt64(tempFile)
if err != nil {
return nil, errors.Wrapf(err, "cannot read temperature from %s", tempFile)
}
m[metric.metric] = float64(temp) / 1000
}
return m, nil
}
// Snapshot takes a snapshot of thermal metrics.
// If there are no thermal sensors, Snapshot does nothing and returns without error.
func (b *SysfsThermalMetrics) Snapshot(ctx context.Context, values *perf.Values) error {
m, err := b.SnapshotValues(ctx)
if err != nil {
return err
}
for metric, value := range m {
values.Append(metric, value)
}
return nil
}
// Stop does nothing.
func (b *SysfsThermalMetrics) Stop(ctx context.Context, values *perf.Values) error {
return nil
}