blob: b5952186e6eec78928d9608ef94a8429d887a858 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"time"
"android.googlesource.com/platform/tools/gpu/client/adb"
"android.googlesource.com/platform/tools/gpu/client/android"
"android.googlesource.com/platform/tools/gpu/client/gapii"
"android.googlesource.com/platform/tools/gpu/framework/app"
"android.googlesource.com/platform/tools/gpu/framework/file"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
)
var (
device string
duration time.Duration
output string
localPort int
observeFrames uint
observeDraws uint
clearCache bool
inputFile string
recordInputs bool
replayInputs bool
useLDPreload bool
)
func init() {
verb := &app.Verb{
Name: "trace",
ShortHelp: "Captures a gfx trace from an application",
Run: doTrace,
}
verb.Flags.StringVar(&device, "device", "", "the device to capture on")
verb.Flags.DurationVar(&duration, "d", 0, "duration to trace for")
verb.Flags.StringVar(&output, "out", "", "the file to generate")
verb.Flags.IntVar(&localPort, "local", 0, "capture a local program instead of using ADB")
verb.Flags.UintVar(&observeFrames, "observeFrames", 0, "capture the framebuffer every n frames (0 to disable)")
verb.Flags.UintVar(&observeDraws, "observeDraws", 0, "capture the framebuffer every n draws (0 to disable)")
verb.Flags.BoolVar(&clearCache, "clearCache", false, "clear package data before running it")
verb.Flags.StringVar(&inputFile, "input-file", "", "the file to use for recorded inputs")
verb.Flags.BoolVar(&recordInputs, "record-inputs", false, "record the inputs to file")
verb.Flags.BoolVar(&replayInputs, "replay-inputs", false, "replay the inputs from file")
verb.Flags.BoolVar(&useLDPreload, "ld-preload", false, "use LD_PRELOAD to register the interceptor library")
app.AddVerb(verb)
}
func doTrace(ctx log.Context, flags flag.FlagSet) error {
if flags.NArg() != 1 && localPort == 0 {
app.Usage(ctx, "Invalid number of arguments. Expected 1, got %d", flags.NArg())
return nil
}
options := gapii.Options{
ObserveFrameFreqency: uint32(observeFrames),
ObserveDrawFrequency: uint32(observeDraws),
}
if localPort != 0 {
return captureLocal(ctx, flags, localPort, options)
}
return captureADB(ctx, flags, options, useLDPreload)
}
func captureLocal(ctx log.Context, flags flag.FlagSet, port int, options gapii.Options) error {
if output == "" {
output = "capture.gfxtrace"
}
return capture(ctx, options, port, output)
}
func captureADB(ctx log.Context, flags flag.FlagSet, options gapii.Options, useLDPreload bool) error {
activity := flags.Arg(0)
d, err := getDevice(ctx, device)
if err != nil {
return err
}
a, err := getAction(ctx, d, activity)
if err != nil {
return err
}
if !useLDPreload && a.Package.Debuggable {
ctx.Print("Package is debuggable")
} else {
err = d.Root(ctx)
switch err {
case nil:
case adb.ErrDeviceNotRooted:
return err
default:
return fmt.Errorf("Failed to restart ADB as root: %v", err)
}
ctx.Print("Device is rooted")
}
// Filenames - if no name specified, use package name.
if output == "" {
output = a.Package.Name + ".gfxtrace"
}
if inputFile == "" {
inputFile = a.Package.Name + ".inputs"
}
if clearCache {
ctx.Print("Clearing package cache")
if err := a.Package.ClearCache(ctx); err != nil {
return err
}
}
if wasScreenOn, _ := d.IsScreenOn(ctx); !wasScreenOn {
defer d.TurnScreenOff(ctx) // Think green!
}
start := gapii.JdwpStart
if useLDPreload {
start = gapii.AdbStart
}
binDir := file.ExecutablePath().Parent()
port, cleanup, err := start(ctx, binDir, a)
if err != nil {
return err
}
defer cleanup(ctx)
ctx, stop := task.WithCancel(ctx)
if recordInputs {
ctx.Print("Starting input recorder")
cleanup, err := startRecordingInputs(ctx, d, inputFile)
if err != nil {
return err
}
defer cleanup()
} else if replayInputs {
ctx.Print("Starting input replayer")
if err := startReplayingInputs(ctx, d, inputFile, stop); err != nil {
return err
}
}
return capture(ctx, options, port, output)
}
func capture(ctx log.Context, options gapii.Options, port int, out string) error {
ctx.Info().S("file", out).Log("Creating file")
os.MkdirAll(filepath.Dir(out), 0755)
file, err := os.Create(out)
if err != nil {
return err
}
defer file.Close()
if duration == 0 {
var cancel task.CancelFunc
ctx, cancel = task.WithCancel(ctx)
go func() {
println("Press enter to stop capturing...")
os.Stdin.Read([]byte{0})
cancel()
}()
} else {
ctx, _ = task.WithTimeout(ctx, duration)
}
_, err = gapii.Capture(ctx, port, file, options)
if err != nil {
return err
}
return nil
}
func getDevice(ctx log.Context, pattern string) (adb.Device, error) {
devices, err := adb.Devices(ctx)
if err != nil {
return nil, err
}
if len(devices) == 0 {
return nil, fmt.Errorf("No devices found")
}
info := ctx.Info()
if info.Active() {
info.Log("Device list:")
for _, test := range devices {
info.S("serial", test.Info().Address.Path).Log("")
}
}
matchingDevices := []adb.Device{}
if pattern == "" {
matchingDevices = devices
} else {
re := regexp.MustCompile("(?i)" + pattern)
for _, test := range devices {
if re.MatchString(fmt.Sprint(test)) {
matchingDevices = append(matchingDevices, test)
}
}
}
if len(matchingDevices) == 0 {
return nil, fmt.Errorf("No devices matching %q found", pattern)
} else if len(matchingDevices) > 1 {
fmt.Println("Matching devices:")
for _, test := range matchingDevices {
fmt.Print(" ")
fmt.Println(test.Info().Address)
}
return nil, fmt.Errorf("Multiple devices matching %q found", pattern)
}
info.WithValue("device", matchingDevices[0]).Log("Tracing")
return matchingDevices[0], nil
}
func getAction(ctx log.Context, d adb.Device, pattern string) (*android.Action, error) {
re := regexp.MustCompile("(?i)" + pattern)
packages, err := d.InstalledPackages(ctx)
if err != nil {
return nil, err
}
if len(packages) == 0 {
return nil, fmt.Errorf("No packages found")
}
matchingActions := []*android.Action{}
for _, p := range packages {
for _, action := range p.Actions {
if re.MatchString(action.String()) {
matchingActions = append(matchingActions, action)
}
}
}
if len(matchingActions) == 0 {
return nil, fmt.Errorf("No actions matching %s found", pattern)
} else if len(matchingActions) > 1 {
fmt.Println("Matching actions:")
for _, test := range matchingActions {
fmt.Print(" ")
fmt.Println(test)
}
return nil, fmt.Errorf("Multiple actions matching %q found", pattern)
}
ctx.Info().WithValue("action", matchingActions[0]).Log("")
return matchingActions[0], nil
}