blob: ba58667d77dce036361aeda100dbd34368d5a20a [file] [log] [blame]
// Copyright 2022 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 lacros
import (
"context"
"math"
"os"
"time"
"chromiumos/tast/common/action"
"chromiumos/tast/common/perf"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/browser"
"chromiumos/tast/local/chrome/browser/browserfixt"
"chromiumos/tast/local/chrome/metrics"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/pointer"
"chromiumos/tast/local/chrome/uiauto/role"
"chromiumos/tast/local/input"
memorymetrics "chromiumos/tast/local/memory/metrics"
"chromiumos/tast/local/tracing"
"chromiumos/tast/testing"
)
const (
videocallURL = "https://storage.googleapis.com/chromiumos-test-assets-public/power_VideoCall/power_VideoCall.html"
docsURL = "http://crospower.page.link/power_VideoCall_doc"
measurementDuration = 60 * time.Second
notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
)
type powerVideocallParams struct {
browserType browser.Type
collectTrace bool
}
func init() {
testing.AddTest(&testing.Test{
Func: PowerVideocall,
LacrosStatus: testing.LacrosVariantExists,
Desc: "Runs a video conference and text input window side-by-side with either ash-chrome and lacros-chrome",
Contacts: []string{"luken@google.com", "hidehiko@chromium.org", "lacros-team@google.com"},
SoftwareDeps: []string{"chrome"},
ServiceDeps: []string{"tast.cros.baserpc.FileSystem"},
Timeout: 5 * time.Minute,
Params: []testing.Param{{
Name: "lacros",
Val: powerVideocallParams{browserType: browser.TypeLacros, collectTrace: false},
Fixture: "lacros",
ExtraSoftwareDeps: []string{"lacros"},
ExtraAttr: []string{"group:crosbolt", "crosbolt_nightly"},
}, {
Name: "ash",
Val: powerVideocallParams{browserType: browser.TypeAsh, collectTrace: false},
Fixture: "chromeLoggedIn",
ExtraAttr: []string{"group:crosbolt", "crosbolt_nightly"},
}, {
Name: "lacros_trace",
Val: powerVideocallParams{browserType: browser.TypeLacros, collectTrace: true},
Fixture: "lacros",
ExtraSoftwareDeps: []string{"lacros"},
ExtraData: []string{tracing.TBMTracedProbesConfigFile,
tracing.TraceProcessorAmd64,
tracing.TraceProcessorArm,
tracing.TraceProcessorArm64},
}, {
Name: "ash_trace",
Val: powerVideocallParams{browserType: browser.TypeAsh, collectTrace: true},
Fixture: "chromeLoggedIn",
ExtraData: []string{tracing.TBMTracedProbesConfigFile,
tracing.TraceProcessorAmd64,
tracing.TraceProcessorArm,
tracing.TraceProcessorArm64},
}},
})
}
func PowerVideocall(ctx context.Context, s *testing.State) {
params := s.Param().(powerVideocallParams)
cr := s.FixtValue().(chrome.HasChrome).Chrome()
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to connect to the test API connection: ", err)
}
ws, err := ash.GetAllWindows(ctx, tconn)
if err != nil {
s.Fatal("Failed to get all windows: ", err)
}
for _, w := range ws {
w.CloseWindow(ctx, tconn)
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
ws, err := ash.GetAllWindows(ctx, tconn)
if err != nil {
return testing.PollBreak(errors.Wrap(err, "failed to get all windows from ash"))
}
if len(ws) > 0 {
return errors.New("waiting to close all windows")
}
return nil
}, &testing.PollOptions{Interval: time.Second, Timeout: 2 * time.Minute}); err != nil {
s.Fatal("Failed to poll to close any excess windows: ", err)
}
memBase, err := memorymetrics.NewBaseMemoryStats(ctx, nil)
if err != nil {
s.Fatal("Failed to get base zram stats: ", err)
}
videoConn, br, cleanup, err := browserfixt.SetUpWithURL(ctx, s.FixtValue(), params.browserType, videocallURL)
if err != nil {
s.Fatal("Failed to set up browser: ", err)
}
defer cleanup(ctx)
defer func() {
videoConn.CloseTarget(ctx)
videoConn.Close()
}()
bTconn, err := br.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to get browser test API connection: ", err)
}
videoWin, err := ash.FindWindow(ctx, tconn, func(window *ash.Window) bool {
if params.browserType == browser.TypeAsh {
return window.WindowType == ash.WindowTypeBrowser
}
return window.WindowType == ash.WindowTypeLacros
})
if err != nil {
s.Fatal("Failed to find video window: ", err)
}
if err := ash.SetWindowStateAndWait(ctx, tconn, videoWin.ID, ash.WindowStateRightSnapped); err != nil {
s.Error("Failed to snap first blank window to the right: ", err)
}
pc := pointer.NewMouse(tconn)
bubble := nodewith.ClassName("PermissionPromptBubbleView").First()
allow := nodewith.Name("Allow").Role(role.Button).Ancestor(bubble)
if err := pc.Click(allow)(ctx); err != nil {
s.Fatal("Failed to click permission bubble: ", err)
}
docsConn, err := br.NewConn(ctx, docsURL, browser.WithNewWindow())
if err != nil {
s.Fatal("Failed to open docs window: ", err)
}
defer func() {
docsConn.CloseTarget(ctx)
docsConn.Close()
}()
docsWin, err := ash.FindWindow(ctx, tconn, func(window *ash.Window) bool {
if params.browserType == browser.TypeAsh {
if window.WindowType == ash.WindowTypeBrowser {
return window != videoWin
}
} else {
if window.WindowType == ash.WindowTypeLacros {
return window != videoWin
}
}
return false
})
if err != nil {
s.Fatal("Failed to find docs window: ", err)
}
if err := ash.SetWindowStateAndWait(ctx, tconn, docsWin.ID, ash.WindowStateLeftSnapped); err != nil {
s.Error("Failed to snap second blank window to the left: ", err)
}
kw, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Failed to create a keyboard: ", err)
}
defer kw.Close()
// Select text input field
if err := pc.Click(nodewith.Name("Edit here").Role(role.TextField))(ctx); err != nil {
s.Fatal("Failed to select input field on docs page: ", err)
}
var sess *tracing.Session
if params.collectTrace {
traceConfigPath := s.DataPath(tracing.TBMTracedProbesConfigFile)
sess, err = tracing.StartSession(ctx, traceConfigPath)
if err != nil {
s.Fatal("Failed to start tracing: ", err)
}
s.Log("Collecting Perfetto trace File at: ", sess.TraceResultFile.Name())
}
histograms, err := metrics.RunAndWaitAll(
ctx,
bTconn,
2*measurementDuration,
func(ctx context.Context) error {
end := time.Now()
for time.Now().Sub(end) < measurementDuration {
if err := uiauto.Combine(
"sleep and type",
action.Sleep(5*time.Second),
kw.TypeAction(notes),
)(ctx); err != nil {
return err
}
}
return nil
},
"Event.Latency.EndToEnd.KeyPress",
)
if err != nil {
if params.collectTrace {
sess.Stop()
}
s.Fatal("Failed to collect metric data: ", err)
}
if params.collectTrace {
if err := sess.Stop(); err != nil {
s.Fatal("Failed to stop the tracing session: ", err)
}
// Transfer trace file to output directory.
if err := os.Rename(sess.TraceResultFile.Name(), s.OutDir()+"/trace.pb"); err != nil {
s.Fatal("Failed to transfer trace file: ", err)
}
}
pv := perf.NewValues()
if err := memorymetrics.LogMemoryStats(ctx, memBase, nil, pv, s.OutDir(), ""); err != nil {
s.Error("Failed to log memory stats: ", err)
}
for _, h := range histograms {
mean, err := h.Mean()
if err != nil {
s.Error("Failed to extract mean from histogram: ", err)
}
pv.Set(
perf.Metric{
Name: h.Name + ".mean",
Unit: "microseconds",
Direction: perf.SmallerIsBetter,
},
mean,
)
totalCount := h.TotalCount()
sampleNum95 := (totalCount * 95) / 100
var max int64
var stdDev float64
var value95 float64
var t int64
for _, b := range h.Buckets {
midpoint := (float64(b.Min) + float64(b.Max)) / 2.0
stdDev = stdDev + (float64(b.Count) * (mean - midpoint) * (mean - midpoint))
max = b.Max
if t < sampleNum95 {
if t+b.Count >= sampleNum95 {
value95 = float64(b.Min) + ((float64(b.Max) - float64(b.Min)) * ((float64(sampleNum95) - float64(t)) / float64(b.Count)))
}
}
t = t + b.Count
}
stdDev = math.Sqrt(stdDev / (float64(totalCount) - 1.0))
pv.Set(
perf.Metric{
Name: h.Name + ".std_dev",
Unit: "microseconds",
Direction: perf.SmallerIsBetter,
},
stdDev,
)
pv.Set(
perf.Metric{
Name: h.Name + ".percent_95",
Unit: "microseconds",
Direction: perf.SmallerIsBetter,
},
value95,
)
pv.Set(
perf.Metric{
Name: h.Name + ".max",
Unit: "microseconds",
Direction: perf.SmallerIsBetter,
},
float64(max),
)
}
if err := pv.Save(s.OutDir()); err != nil {
s.Error("Failed to save the perf data: ", err)
}
}