blob: 0177b50e5c50fb1cb45be7df4c8ef39dd9d5ff67 [file] [log] [blame]
// Copyright 2021 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 memory
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strconv"
"strings"
"chromiumos/tast/common/perf"
"chromiumos/tast/errors"
)
// ZramMmStat holds statistics from /sys/block/zram*/mm_stat.
type ZramMmStat struct {
OrigDataSize, ComprDataSize, MemUsedTotal, MemLimit, MemUsedMax, SamePages, PagesCompacted uint64
// Some fields have been introduced by newer kernels, make them optional.
HugePages, HugePagesSince *uint64 `json:",omitempty"`
}
// NewZramMmStat parses /sys/block/zram*/mm_stat to create a ZramMmStat.
func NewZramMmStat() (*ZramMmStat, error) {
files, err := filepath.Glob("/sys/block/zram*/mm_stat")
if err != nil {
return nil, errors.Wrap(err, "failed to find zram<id>/mm_stat file")
} else if len(files) != 1 {
return nil, errors.Errorf("expected 1 zram device, got %d", len(files))
}
mmStat, err := ioutil.ReadFile(files[0])
if err != nil {
return nil, errors.Wrap(err, "failed to read mm_stat")
}
fields := strings.Fields(string(mmStat))
if len(fields) < 7 {
return nil, errors.Errorf("expected at least 7 fields in mm_stat file, got %d", len(fields))
}
parsedFields := make([]uint64, len(fields))
for i, stat := range fields {
parsed, err := strconv.ParseUint(stat, 10, 64)
if err != nil {
return nil, errors.Errorf("failed to parse field %d in mm_stat %q", i, stat)
}
parsedFields[i] = parsed
}
stats := &ZramMmStat{
OrigDataSize: parsedFields[0],
ComprDataSize: parsedFields[1],
MemUsedTotal: parsedFields[2],
MemLimit: parsedFields[3],
MemUsedMax: parsedFields[4],
SamePages: parsedFields[5],
PagesCompacted: parsedFields[6],
}
if len(parsedFields) > 7 {
stats.HugePages = &parsedFields[7]
}
if len(parsedFields) > 8 {
stats.HugePagesSince = &parsedFields[8]
}
return stats, nil
}
// ZramMmStatMetrics writes a JSON file containing statistics from
// /sys/block/zram/mm_stat. If outdir is "", then no logs are written.
func ZramMmStatMetrics(ctx context.Context, p *perf.Values, outdir, suffix string) error {
stat, err := NewZramMmStat()
if err != nil {
return err
}
if len(outdir) > 0 {
statJSON, err := json.MarshalIndent(stat, "", " ")
if err != nil {
return errors.Wrap(err, "failed to serialize mm_stat metrics to JSON")
}
filename := fmt.Sprintf("zram_mm_stat%s.json", suffix)
if err := ioutil.WriteFile(path.Join(outdir, filename), statJSON, 0644); err != nil {
return errors.Wrapf(err, "failed to write zram mm_stats to %s", filename)
}
}
if p == nil {
// No perf.Values, so exit without computing the metrics.
return nil
}
p.Set(
perf.Metric{
Name: fmt.Sprintf("zram_original%s", suffix),
Unit: "MiB",
Direction: perf.SmallerIsBetter,
},
float64(stat.OrigDataSize)/MiB,
)
p.Set(
perf.Metric{
Name: fmt.Sprintf("zram_compressed%s", suffix),
Unit: "MiB",
Direction: perf.SmallerIsBetter,
},
float64(stat.ComprDataSize)/MiB,
)
return nil
}