blob: d404a83bfa50b3a4bacb158d5d9a5fb7d452e3a6 [file] [log] [blame] [edit]
// 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 multivm
import (
"context"
"fmt"
"strconv"
"time"
"chromiumos/tast/common/perf"
"chromiumos/tast/local/crostini"
"chromiumos/tast/local/memory"
"chromiumos/tast/local/memory/kernelmeter"
"chromiumos/tast/local/memory/memoryuser"
"chromiumos/tast/local/memory/metrics"
"chromiumos/tast/local/multivm"
"chromiumos/tast/testing"
)
type lifecycleShiftingParam struct {
inHost, inCrostini, inARC bool
}
func init() {
testing.AddTest(&testing.Test{
Func: LifecycleShifting,
Desc: "Create many Apps, Tabs, Processes in turn across multiple VMs, and see how many can stay alive",
Contacts: []string{"cwd@google.com", "cros-platform-kernel-core@google.com"},
Attr: []string{"group:crosbolt", "crosbolt_nightly"},
Timeout: 30 * time.Minute,
SoftwareDeps: []string{"chrome"},
Vars: []string{"multivm.minimize_arc"},
Params: []testing.Param{{
Name: "arc_host",
Pre: multivm.ArcStarted(),
Val: &lifecycleShiftingParam{inARC: true, inHost: true},
ExtraSoftwareDeps: []string{"android_vm"},
ExtraData: []string{
memoryuser.AllocPageFilename,
memoryuser.JavascriptFilename,
},
}, {
Name: "crostini_host",
Pre: multivm.CrostiniStarted(),
Val: &lifecycleShiftingParam{inCrostini: true, inHost: true},
ExtraSoftwareDeps: []string{"android_vm"},
ExtraData: []string{
crostini.GetContainerMetadataArtifact("buster", false),
crostini.GetContainerRootfsArtifact("buster", false),
memoryuser.AllocPageFilename,
memoryuser.JavascriptFilename,
},
ExtraHardwareDeps: crostini.CrostiniStable,
}},
})
}
func LifecycleShifting(ctx context.Context, s *testing.State) {
pre := s.PreValue().(*multivm.PreData)
param := s.Param().(*lifecycleShiftingParam)
preARC := multivm.ARCFromPre(pre)
preCrostini := multivm.CrostiniFromPre(pre)
minimizeArc := false
minimizeArcString, ok := s.Var("multivm.minimize_arc")
if ok {
if minimize, err := strconv.ParseBool(minimizeArcString); err == nil {
minimizeArc = minimize
}
}
info, err := kernelmeter.MemInfo()
if err != nil {
s.Fatal("Failed to get /proc/meminfo: ", err)
}
basemem, err := metrics.NewBaseMemoryStats(ctx, preARC)
if err != nil {
s.Fatal("Failed to retrieve base memory stats: ", err)
}
// Use a PageReclaimLimit to avoid OOMing in the host. Will be composed with
// VM limits so that we don't OOM in the host or any VM.
hostLimit := memory.NewPageReclaimLimit()
var server *memoryuser.MemoryStressServer
numTypes := 0
if param.inHost {
server = memoryuser.NewMemoryStressServer(s.DataFileSystem())
defer server.Close()
numTypes++
}
if param.inARC {
numTypes++
}
if param.inCrostini {
numTypes++
}
if numTypes == 0 {
s.Fatal("No lifecycle unit types")
}
// Created tabs/apps/etc. should have memory that is a bit compressible.
// We use the same value as the low compress ratio in
// platform.MemoryStressBasic.
const compressRatio = 0.67
taskAllocMiB := (2 * int64(info.Total) / 100) / memory.MiB
var tasks []memoryuser.MemoryTask
var appsAliveTasks []memoryuser.KillableTask
var tabsAliveTasks []memoryuser.KillableTask
var procsAliveTasks []memoryuser.KillableTask
var appsAliveMetrics []*memoryuser.StillAliveMetricTask
var tabsAliveMetrics []*memoryuser.StillAliveMetricTask
var procsAliveMetrics []*memoryuser.StillAliveMetricTask
for i := 0; i < 3; i++ {
const numTasks = 50
if param.inHost {
for j := 0; j < numTasks/numTypes; j++ {
task := server.NewMemoryStressTask(int(taskAllocMiB), compressRatio, hostLimit)
tabsAliveTasks = append(tabsAliveTasks, task)
tasks = append(tasks, task)
}
task := memoryuser.NewStillAliveMetricTask(
tabsAliveTasks,
fmt.Sprintf("tabs_alive_%d", i),
)
tabsAliveMetrics = append(tabsAliveMetrics, task)
tasks = append(tasks, task)
}
if param.inARC {
for j := 0; j < numTasks/numTypes; j++ {
task := memoryuser.NewArcLifecycleTask(len(appsAliveTasks), taskAllocMiB*memory.MiB, compressRatio, hostLimit, minimizeArc)
appsAliveTasks = append(appsAliveTasks, task)
tasks = append(tasks, task)
}
task := memoryuser.NewStillAliveMetricTask(
appsAliveTasks,
fmt.Sprintf("apps_alive_%d", i),
)
appsAliveMetrics = append(appsAliveMetrics, task)
tasks = append(tasks, task)
}
if param.inCrostini {
for j := 0; j < numTasks/numTypes; j++ {
task := memoryuser.NewCrostiniLifecycleTask(preCrostini, len(procsAliveTasks), taskAllocMiB, compressRatio, hostLimit)
procsAliveTasks = append(procsAliveTasks, task)
tasks = append(tasks, task)
}
task := memoryuser.NewStillAliveMetricTask(
procsAliveTasks,
fmt.Sprintf("procs_alive_%d", i),
)
procsAliveMetrics = append(procsAliveMetrics, task)
tasks = append(tasks, task)
}
}
if param.inHost {
task := memoryuser.NewMinStillAliveMetricTask(tabsAliveMetrics, "tabs_alive_min")
tasks = append(tasks, task)
}
if param.inARC {
task := memoryuser.NewMinStillAliveMetricTask(appsAliveMetrics, "apps_alive_min")
tasks = append(tasks, task)
if err := memoryuser.InstallArcLifecycleTestApps(ctx, preARC, len(appsAliveTasks)); err != nil {
s.Fatal("Failed to install ArcLifecycleTestApps: ", err)
}
}
if param.inCrostini {
task := memoryuser.NewMinStillAliveMetricTask(procsAliveMetrics, "procs_alive_min")
tasks = append(tasks, task)
if err := memoryuser.InstallCrostiniLifecycle(ctx, preCrostini); err != nil {
s.Fatal("Failed to install Crostini lifecycle unit: ", err)
}
defer func() {
if err := memoryuser.UninstallCrostiniLifecycle(ctx, preCrostini); err != nil {
s.Error("Failed to uninstall Crostini lifecycle unit: ", err)
}
}()
}
p := perf.NewValues()
// Run all the tasks.
rp := &memoryuser.RunParameters{
UseARC: preARC != nil,
ExistingChrome: pre.Chrome,
ExistingARC: preARC,
ExistingPerfValues: p,
}
if err := memoryuser.RunTest(ctx, s.OutDir(), tasks, rp); err != nil {
s.Fatal("RunTest failed: ", err)
}
if err := metrics.LogMemoryStats(ctx, basemem, preARC, p, s.OutDir(), ""); err != nil {
s.Error("Failed to collect memory metrics")
}
if err := p.Save(s.OutDir()); err != nil {
s.Error("Failed to save perf.Values: ", err)
}
}