blob: bdbf193caffdaa3010ec3c70a9a7655e8af2ea24 [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package osperf
import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/structpb"
"go.chromium.org/tast-tests/cros/common/chrome/extension"
"go.chromium.org/tast-tests/cros/common/perf"
"go.chromium.org/tast-tests/cros/remote/dutfs"
"go.chromium.org/tast-tests/cros/remote/osperf"
"go.chromium.org/tast-tests/cros/services/cros/ui"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/rpc"
"go.chromium.org/tast/core/ssh/linuxssh"
"go.chromium.org/tast/core/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: TabOpenLatencyPerf,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Measures tab open latency remotely to get stable results quickly",
BugComponent: "b:167279", // ChromeOS > Platform > baseOS > Performance
Contacts: []string{"baseos-perf@google.com", "hikalium@google.com"},
Attr: []string{"group:mainline", "informational"},
Data: []string{
"tab_open_latency_perf/manifest.json",
"tab_open_latency_perf/bench.js",
"tab_open_latency_perf/background.js",
"tab_open_latency_perf/bench.html",
},
SoftwareDeps: []string{"chrome"},
ServiceDeps: []string{
"tast.cros.browser.ChromeService",
"tast.cros.ui.ConnService",
"tast.cros.ui.TconnService",
},
})
}
func TabOpenLatencyPerf(ctx context.Context, s *testing.State) {
d := s.DUT()
cl, err := rpc.Dial(ctx, s.DUT(), s.RPCHint())
if err != nil {
s.Fatal("Failed to dial to DUT for remote file system: ", err)
}
defer cl.Close(ctx)
const benchDir = "/tmp/bluebench" // Using /tmp dir to make it runnable with read-only rootfs.
fs := dutfs.NewClient(cl.Conn)
// (Re)create benchDir.
if dirExists, err := fs.Exists(ctx, benchDir); err != nil {
s.Fatal("Failed to check the existence of the USB device's original mount point: ", err)
} else if dirExists {
if err := fs.RemoveAll(ctx, benchDir); err != nil {
s.Fatalf("Failed to mkdir %s: %v", benchDir, err)
}
}
if err := fs.MkDir(ctx, benchDir, os.FileMode(0750)); err != nil {
s.Fatalf("Failed to mkdir %s: %v", benchDir, err)
}
dataPaths := s.DataPaths()
dataMap := make(map[string]string, len(dataPaths))
const dataDir = "tab_open_latency_perf/"
for name, path := range dataPaths {
if !strings.HasPrefix(name, dataDir) {
s.Fatalf("The data path should start with %s but got %s", dataDir, name)
}
dataMap[path] = filepath.Join(benchDir, strings.TrimPrefix(name, dataDir))
}
if bytes, err := linuxssh.PutFiles(ctx, d.Conn(), dataMap, linuxssh.PreserveSymlinks); err != nil {
s.Fatal("Failed to copy bluebench files via ssh")
} else if bytes == 0 {
s.Fatal("zero bytes transferred while copying the bluebench files")
}
cr := runBluebench(ctx, s, cl, benchDir, filepath.Dir(dataPaths[dataDir+"manifest.json"]))
defer cr.Close(ctx, &emptypb.Empty{})
}
// queryTestConnTabIDs queries the tab IDs which is used for TconnService.
func queryTestConnTabIDs(ctx context.Context, tconn ui.TconnServiceClient, condition string) ([]int, error) {
res, err := tconn.Call(ctx, &ui.CallRequest{
Fn: `async () => {
let tabs = await tast.promisify(chrome.tabs.query)({` + condition + `});
return tabs.map((tab) => tab.id);
}`,
Args: []*structpb.Value{},
})
if err != nil {
return nil, errors.Wrap(err, "cannot query tab list")
}
ids := res.AsInterface().([]interface{})
out := make([]int, len(ids))
for i, id := range ids {
out[i] = int(id.(float64))
}
return out, nil
}
type tabOpenLatencyTestResult struct {
TabOpenLatencyMean float64 `json:"tab_open_latency_mean"`
TabOpenLatencyMax float64 `json:"tab_open_latency_max"`
TabOpenLatencyMin float64 `json:"tab_open_latency_min"`
TotalDuration float64 `json:"total_duration"`
TotalRunCount float64 `json:"total_run_count"`
ConvergedMeans []float64 `json:"converged_means"`
ResultLog string `json:"result_log"`
}
func runBluebench(ctx context.Context, s *testing.State, cl *rpc.Client, extDir, hostExtDir string) ui.ChromeServiceClient {
s.Log("Setting up bluebench extension")
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
extID, err := extension.ComputeExtensionID(hostExtDir)
if err != nil {
s.Fatalf("Failed to compute extension ID for %v: %v", extDir, err)
}
cr := ui.NewChromeServiceClient(cl.Conn)
req := ui.NewRequest{UnpackedExtensions: []string{extDir}}
if _, err := cr.New(ctx, &req); err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
conn := ui.NewConnServiceClient(cl.Conn)
benchURL := "chrome-extension://" + extID + "/bench.html"
resTab, err := osperf.OpenBenchPage(ctx, benchURL, conn)
if err != nil {
s.Fatal("Failed to open new Tab: ", err)
}
s.Log("Running the benchmark")
resRun, err := conn.Call(ctx, &ui.ConnCallRequest{
Id: resTab.Id,
Fn: `
() => {
// document.getElementById("numConvergedResultsInput").value = "1";
return window.startBench();
}
`,
})
if err != nil {
s.Fatal("startBench failed: ", err)
}
s.Log("Completed the benchmark")
resMap := resRun.AsInterface().(map[string]interface{})
jsonString, err := json.Marshal(resMap)
info := tabOpenLatencyTestResult{}
if err = json.Unmarshal(jsonString, &info); err != nil {
s.Fatal("Failed to parse tabOpenLatencyTestResult: ", err)
}
if err := ioutil.WriteFile(filepath.Join(s.OutDir(), "bluebench_log.txt"),
[]byte(info.ResultLog), 0644); err != nil {
s.Error("Failed to write bluebench_log.txt: ", err)
}
dumpHardwareInformation(ctx, s)
// Save the result as results-chart.json.
pv := perf.NewValues()
pv.Set(perf.Metric{
Name: "TabOpenLatencyMean",
Unit: "milliseconds",
Direction: perf.SmallerIsBetter,
}, info.TabOpenLatencyMean)
pv.Set(perf.Metric{
Name: "TabOpenLatencyMax",
Unit: "milliseconds",
Direction: perf.SmallerIsBetter,
}, info.TabOpenLatencyMax)
pv.Set(perf.Metric{
Name: "TabOpenLatencyMin",
Unit: "milliseconds",
Direction: perf.SmallerIsBetter,
}, info.TabOpenLatencyMin)
pv.Set(perf.Metric{
Name: "TotalDuration",
Unit: "milliseconds",
Direction: perf.SmallerIsBetter,
}, info.TotalDuration)
pv.Set(perf.Metric{
Name: "TotalRunCount",
Unit: "count",
Direction: perf.SmallerIsBetter,
}, info.TotalRunCount)
pv.Set(perf.Metric{
Name: "ConvergedMeans",
Unit: "milliseconds",
Direction: perf.SmallerIsBetter,
Multiple: true,
}, info.ConvergedMeans...)
pv.Save(s.OutDir())
return cr
}
func dumpHardwareInformation(ctx context.Context, s *testing.State) {
dumpCommandMap := map[string]string{
"crossystem.txt": "crossystem",
"vpd.txt": "vpd -l",
"uname.txt": "uname -a",
"meminfo.txt": "cat /proc/meminfo",
"lscpu.txt": "lscpu",
"cmdline.txt": "cat /proc/cmdline",
"kernel_version.txt": "cat /proc/version",
"cpuinfo.txt": "cat /proc/cpuinfo",
"ifconfig.txt": "ifconfig",
"mount.txt": "mount",
"current_kernel_part_hash.txt": "md5sum $(rootdev -s | sed -e 's/5$/4/' -e 's/3$/2/')",
"current_kernel_verification.txt": "vbutil_kernel --verify $(rootdev -s | sed -e 's/5$/4/' -e 's/3$/2/')",
"rootdev.txt": "rootdev -s",
"bootid.txt": "cat /proc/sys/kernel/random/boot_id",
"lsb_release.txt": "cat /etc/lsb-release",
"messages.txt": "cat /var/log/messages",
}
dut := s.DUT()
for name, command := range dumpCommandMap {
if err := osperf.DumpCommandOutput(ctx, dut, filepath.Join(s.OutDir(), name), command); err != nil {
s.Fatal("Failed to dump info: ", err)
}
}
}