blob: dbdbab3a4a9b185ab73cc11c9a0d753f9957a65c [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 arc
import (
"context"
"path/filepath"
"strconv"
"time"
"chromiumos/tast/common/media/caps"
"chromiumos/tast/common/perf"
"chromiumos/tast/ctxutil"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/power"
"chromiumos/tast/local/power/setup"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: PowerCameraRecordingPerf,
Desc: "Measures the battery drain during camera recording at 30 FPS",
Contacts: []string{
"lnishan@chromium.org",
"arcvm-eng@google.com",
},
SoftwareDeps: []string{"chrome", caps.BuiltinOrVividCamera},
Fixture: "arcBootedWithDisableSyncFlags",
Attr: []string{"group:crosbolt", "crosbolt_nightly"},
Params: []testing.Param{{
ExtraSoftwareDeps: []string{"android_p"},
ExtraHardwareDeps: hwdep.D(hwdep.ForceDischarge()),
Val: setup.ForceBatteryDischarge,
}, {
Name: "vm",
ExtraSoftwareDeps: []string{"android_vm"},
ExtraHardwareDeps: hwdep.D(hwdep.ForceDischarge()),
Val: setup.ForceBatteryDischarge,
}, {
Name: "nobatterymetrics",
ExtraSoftwareDeps: []string{"android_p"},
ExtraHardwareDeps: hwdep.D(hwdep.NoForceDischarge()),
Val: setup.NoBatteryDischarge,
}, {
Name: "vm_nobatterymetrics",
ExtraSoftwareDeps: []string{"android_vm"},
ExtraHardwareDeps: hwdep.D(hwdep.NoForceDischarge()),
Val: setup.NoBatteryDischarge,
}},
Timeout: 10 * time.Minute,
})
// TODO(b/153129376): Extend test to record with 30 and 60 FPS.
}
func PowerCameraRecordingPerf(ctx context.Context, s *testing.State) {
const (
cameraAppActivity = ".CameraActivity"
cameraAppApk = "ArcCameraFpsTest.apk"
cameraAppPackage = "org.chromium.arc.testapp.camerafps"
intentGetDroppedFrames = "org.chromium.arc.testapp.camerafps.ACTION_GET_NUM_DROPPED_FRAMES"
intentGetHistogram = "org.chromium.arc.testapp.camerafps.ACTION_GET_HISTOGRAM"
intentGetTotalFrames = "org.chromium.arc.testapp.camerafps.ACTION_GET_NUM_FRAMES"
intentGetRecordingSize = "org.chromium.arc.testapp.camerafps.ACTION_GET_RECORDING_SIZE"
intentResetData = "org.chromium.arc.testapp.camerafps.ACTION_RESET_HISTOGRAM"
intentSetFPS = "org.chromium.arc.testapp.camerafps.ACTION_SET_TARGET_FPS"
intentStartRecording = "org.chromium.arc.testapp.camerafps.ACTION_START_RECORDING"
intentStopRecording = "org.chromium.arc.testapp.camerafps.ACTION_STOP_RECORDING"
minExpectedFileSize = 1024 * 1024 // 1 MB
targetFPS = "30"
)
// Give cleanup actions a minute to run, even if we fail by exceeding our
// deadline.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, time.Minute)
defer cancel()
cr := s.FixtValue().(*arc.PreData).Chrome
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
sup, cleanup := setup.New("camera recording power and fps")
defer func(ctx context.Context) {
if err := cleanup(ctx); err != nil {
s.Error("Cleanup failed: ", err)
}
}(cleanupCtx)
batteryMode := s.Param().(setup.BatteryDischargeMode)
sup.Add(setup.PowerTest(ctx, tconn, setup.PowerTestOptions{
Wifi: setup.DisableWifiInterfaces, Battery: batteryMode, NightLight: setup.DisableNightLight}))
// Install camera testing app.
a := s.FixtValue().(*arc.PreData).ARC
sup.Add(setup.InstallApp(ctx, a, arc.APKPath(cameraAppApk), cameraAppPackage))
// Grant permissions to activity.
sup.Add(setup.GrantAndroidPermission(ctx, a, cameraAppPackage, "android.permission.CAMERA"))
sup.Add(setup.GrantAndroidPermission(ctx, a, cameraAppPackage, "android.permission.RECORD_AUDIO"))
sup.Add(setup.GrantAndroidPermission(ctx, a, cameraAppPackage, "android.permission.READ_EXTERNAL_STORAGE"))
sup.Add(setup.GrantAndroidPermission(ctx, a, cameraAppPackage, "android.permission.WRITE_EXTERNAL_STORAGE"))
// Wait until CPU is cooled down.
if _, err := power.WaitUntilCPUCoolDown(ctx, power.DefaultCoolDownConfig(power.CoolDownPreserveUI)); err != nil {
s.Error("CPU failed to cool down: ", err)
}
// Start camera testing app.
sup.Add(setup.StartActivity(ctx, tconn, a, cameraAppPackage, cameraAppActivity))
if err := sup.Check(ctx); err != nil {
s.Fatal("Setup failed: ", err)
}
const (
iterationCount = 30
iterationDuration = 2 * time.Second
afterBootWarmupDuration = 30 * time.Second
cameraWarmupDuration = 30 * time.Second
)
s.Log("Warmup: Waiting for Android to settle down")
if err := testing.Sleep(ctx, afterBootWarmupDuration); err != nil {
s.Fatal("Failed to sleep: ", err)
}
s.Log("Set target FPS:", targetFPS)
if _, err := a.BroadcastIntent(ctx, intentSetFPS, "--ei", "fps", targetFPS); err != nil {
s.Fatal("Could not send intent: ", err)
}
resolution, err := a.BroadcastIntentGetData(ctx, intentGetRecordingSize)
if err != nil {
s.Fatal("Failed to query resolution from activity: ", err)
}
s.Log("Camera recording resolution: ", resolution)
// Create metrics. We report separately for each target FPS.
numFramesMetric := perf.Metric{Name: "total_num_frames", Unit: "frames", Direction: perf.BiggerIsBetter}
numDroppedFramesMetric := perf.Metric{Name: "num_dropped_frames", Unit: "frames", Direction: perf.SmallerIsBetter}
frameDropRatioMetric := perf.Metric{Name: "frame_drop_ratio", Unit: "ratio", Direction: perf.SmallerIsBetter}
powerMetrics, err := perf.NewTimeline(ctx, power.TestMetrics(), perf.Interval(iterationDuration))
if err != nil {
s.Fatal("Failed to build metrics: ", err)
}
if err := powerMetrics.Start(ctx); err != nil {
s.Fatal("Failed to start metrics: ", err)
}
outputFile, err := a.BroadcastIntentGetData(ctx, intentStartRecording)
if err != nil {
s.Fatal("Could not send intent: ", err)
}
filePath := filepath.Join("files/DCIM", outputFile)
s.Log("Recording to file: ", filePath)
s.Log("Warmup: Waiting a bit before starting the measurement")
if err := testing.Sleep(ctx, cameraWarmupDuration); err != nil {
s.Fatal("Failed to sleep: ", err)
}
s.Log("Starting measurement")
if _, err = a.BroadcastIntent(ctx, intentResetData); err != nil {
s.Fatal("Could not send intent: ", err)
}
// Keep camera running and record power usage.
if err := powerMetrics.StartRecording(ctx); err != nil {
s.Fatal("Failed to start recording: ", err)
}
if err := testing.Sleep(ctx, iterationCount*iterationDuration); err != nil {
s.Fatal("Failed to sleep: ", err)
}
p, err := powerMetrics.StopRecording(ctx)
if err != nil {
s.Fatal("Error while recording power metrics: ", err)
}
droppedFrames := 0
if o, err := a.BroadcastIntentGetData(ctx, intentGetDroppedFrames); err != nil {
s.Fatal("Could not send intent: ", err)
} else if droppedFrames, err = strconv.Atoi(o); err != nil {
s.Fatal("Unexpected result from intent " + intentGetDroppedFrames + ": " + o)
}
totalFrames := 0
if o, err := a.BroadcastIntentGetData(ctx, intentGetTotalFrames); err != nil {
s.Fatal("Could not send intent: ", err)
} else if totalFrames, err = strconv.Atoi(o); err != nil {
s.Fatal("Unexpected result from intent " + intentGetTotalFrames + ": " + o)
}
p.Set(numFramesMetric, float64(totalFrames))
p.Set(numDroppedFramesMetric, float64(droppedFrames))
if totalFrames == 0 {
s.Fatal("Camera app did not receive any frames")
} else {
p.Set(frameDropRatioMetric, float64(droppedFrames)/float64(totalFrames))
}
// Print frame duration histogram to log file.
hist, err := a.BroadcastIntentGetData(ctx, intentGetHistogram)
if err != nil {
s.Fatal("Could not send intent: ", err)
}
s.Logf("Frame duration histogram: %q", hist)
if _, err = a.BroadcastIntent(ctx, intentStopRecording); err != nil {
s.Fatal("Could not send intent: ", err)
}
// Check if video file was generated.
fileSize, err := arc.PkgFileSize(ctx, cr.NormalizedUser(), cameraAppPackage, filePath)
if err != nil {
s.Fatal("Could not determine size of video recording: ", err)
}
if fileSize < minExpectedFileSize {
s.Fatalf("Video recording file is smaller than expected: got %d, want >= %d", fileSize, minExpectedFileSize)
}
if err := p.Save(s.OutDir()); err != nil {
s.Error("Failed saving perf data: ", err)
}
}