blob: 802ec327d173718c847c5a061f281e344504da2d [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 cellular
import (
"context"
"fmt"
"net"
"time"
"github.com/golang/protobuf/ptypes/empty"
"go.chromium.org/tast-tests/cros/common/perf"
cp "go.chromium.org/tast-tests/cros/common/power"
"go.chromium.org/tast-tests/cros/remote/cellular/callbox/manager"
"go.chromium.org/tast-tests/cros/remote/cellular/modulepower"
"go.chromium.org/tast-tests/cros/remote/firmware/reporters"
"go.chromium.org/tast-tests/cros/services/cros/power"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/rpc"
"go.chromium.org/tast/core/ssh"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/cellularconst"
"go.chromium.org/tast/core/testing/hwdep"
)
const runIterations = 5
type modulePowerTestCase struct {
connectionOptions *manager.ConfigureCallboxRequestBody
initState initFunction
config *modulepower.Config
testTime time.Duration
}
func init() {
testing.AddTest(&testing.Test{
Func: ModulePower,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Verifies that the power consumption of the DUT is in the expected range",
Contacts: []string{"cros-cellular-core@google.com", "jstanko@google.com"},
BugComponent: "b:167157", // ChromeOS > Platform > Connectivity > Cellular
Attr: []string{"group:cellular", "cellular_callbox", "cellular_cmw_callbox", "cellular_cmx_callbox", "cellular_run_isolated", "cellular_power"},
ServiceDeps: []string{
"tast.cros.cellular.RemoteCellularService",
"tast.cros.power.RecorderService",
"tast.cros.power.DeviceSetupService",
},
SoftwareDeps: []string{"chrome"},
// Restrict tests to models that we have deployed intrusive measurements for.
HardwareDeps: hwdep.D(hwdep.Model("crota", "redrix", "rusty")),
Fixture: "callboxManagedFixture",
Timeout: 15 * time.Minute,
Vars: []string{"servo"},
Params: []testing.Param{
{
Name: "low_power",
ExtraData: []string{"cellular_power_crota_FM101.xml", "cellular_power_redrix_FM350.xml", "cellular_power_rusty_EM060.xml"},
Val: modulePowerTestCase{
connectionOptions: &manager.ConfigureCallboxRequestBody{
CellularType: manager.CellularTechnologyLTE,
Parameters: []manager.CellConfiguration{
manager.NewLteCellConfiguration(
manager.BandOption(3),
),
},
},
initState: disable(),
testTime: 120 * time.Second,
config: &modulepower.Config{
// Unfortunately we can't set the measurement interval too low without
// causing the snapshot to fail.
MeasurementInterval: 400 * time.Millisecond,
},
},
},
},
})
}
// initFunction is a function that puts the device in some steady state before performing measurements.
type initFunction func(context.Context, *testing.State)
func disable() initFunction {
return func(ctx context.Context, s *testing.State) {
tf := s.FixtValue().(*manager.TestFixture)
if _, err := tf.RemoteCellularClient.Disable(ctx, &empty.Empty{}); err != nil {
s.Fatal("Failed to disable cellular: ", err)
}
}
}
func ModulePower(ctx context.Context, s *testing.State) {
tc := s.Param().(modulePowerTestCase)
tf := s.FixtValue().(*manager.TestFixture)
dutConn := s.DUT().Conn()
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 30*time.Second)
defer cancel()
// Connect to the labstation/proxy host.
sshOptions := &ssh.Options{
KeyDir: s.DUT().KeyDir(),
KeyFile: s.DUT().KeyFile(),
ProxyCommand: s.DUT().ProxyCommand(),
}
// Get the servo hostname from the servo arg.
hostname, _ := s.Var("servo")
if host, _, err := net.SplitHostPort(hostname); err != nil {
s.Fatal("Failed to parse servo hostname")
} else if host != "localhost" && host != "127.0.0.1" {
// If host is not localhost, then we should replace port number with 22, as normally
// the servo port is the port that servod session is running on.
hostname = fmt.Sprintf("%s:%d", host, 22)
}
if err := ssh.ParseTarget(hostname, sshOptions); err != nil {
s.Fatal("Failed to parse labstation ssh target: ", err)
}
proxy, err := ssh.New(ctx, sshOptions)
if err != nil {
s.Fatal("Failed to connect to labstation phone host over ssh: ", err)
}
// Prepare data path on labstation/power measurement proxy.
model, err := reporters.New(s.DUT()).Model(ctx)
if err != nil {
s.Fatal("Failed to get model name: ", err)
}
// Get Modem type to apply throughput values accordingly.
resp, err := tf.RemoteCellularClient.QueryModemType(ctx, &empty.Empty{})
if err != nil {
s.Fatal("Failed to get modemType: ", err)
}
modemType := cellularconst.ModemType(resp.ModemType)
// Create sweetberry recorder for module power.
configFile := s.DataPath(fmt.Sprintf("cellular_power_%s_%s.xml", model, &modemType))
recorder, err := modulepower.NewSweetberryRecorder(ctx, proxy, configFile, tc.config)
if err != nil {
s.Fatal("Failed to create sweetberry recorder: ", err)
}
defer recorder.Close(cleanupCtx)
// Initialize remote power client for local measurements.
cl, err := rpc.Dial(ctx, s.DUT(), s.RPCHint())
if err != nil {
s.Fatal("Failed to connect to DUT RPC service: ", err)
}
defer cl.Close(cleanupCtx)
powerClient := power.NewRecorderServiceClient(cl.Conn)
if _, err := powerClient.Create(ctx, &power.RecorderRequest{IntervalSec: 5}); err != nil {
s.Fatal("Failed to set up recorder: ", err)
}
// Configure DUT for power settings.
ds := power.NewDeviceSetupServiceClient(cl.Conn)
setupRequest := &power.DeviceSetupRequest{
Ui: power.UIMode_DISABLE_UI,
ScreenBrightness: power.ScreenMode_ZERO_SCREEN_BRIGHTNESS,
Wifi: power.WifiMode_DISABLE_WIFI,
}
// Setting up a DUT remotely according to the setup request.
if _, err = ds.Setup(ctx, setupRequest); err != nil {
s.Fatal("Failed to setup DUT: ", err)
}
defer ds.Cleanup(ctx, &empty.Empty{})
// Configure the requested network on the callbox.
if err := tf.ConnectToCallbox(ctx, dutConn, tc.connectionOptions); err != nil {
s.Fatal("Failed to initialize cellular connection: ", err)
}
if err := recorder.Start(ctx); err != nil {
s.Fatal("Failed to start power measurement: ", err)
}
testing.ContextLog(ctx, "Start recording power metrics")
if _, err := powerClient.Start(ctx, &empty.Empty{}); err != nil {
s.Fatal("Failed to start recording power metrics: ", err)
}
s.Log("Initializing DUT to steady state")
tc.initState(ctx, s)
testing.ContextLog(ctx, "Cooling down device")
if _, err := powerClient.Cooldown(ctx, &empty.Empty{}); err != nil {
s.Log("Failed to cooldown device: ", err)
}
s.Log("Measuring power for ", tc.testTime)
startTime := time.Now()
// GoBigSleepLint: This is the length of the test to measure.
testing.Sleep(ctx, tc.testTime)
endTime := time.Now()
fullResults, err := recorder.Stop(ctx)
if err != nil {
s.Fatal("Failed to stop power measurement: ", err)
}
localResults, err := powerClient.Stop(ctx, &empty.Empty{})
if err != nil {
s.Fatal("Failed to stop local recorder: ", err)
}
results, err := cp.TrimSubtestResults(startTime, endTime, fullResults)
if err != nil {
s.Fatal("Failed to trim results: ", err)
}
// Merge values after trimming since we may want the local power results over the entire test.
fullResults.Merge(perf.NewValuesFromProto(localResults.GetPerfMetrics()))
if err = results.Save(s.OutDir()); err != nil {
s.Fatal("Failed to save perf data for crosbolt: ", err)
}
}