blob: 04860c22539d0d15c332f7319e544827d1152d19 [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"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"time"
"android.googlesource.com/platform/tools/gpu/client/adb"
"android.googlesource.com/platform/tools/gpu/client/android"
"android.googlesource.com/platform/tools/gpu/client/android/apk"
"android.googlesource.com/platform/tools/gpu/client/bind"
"android.googlesource.com/platform/tools/gpu/client/gapii"
"android.googlesource.com/platform/tools/gpu/client/process"
"android.googlesource.com/platform/tools/gpu/framework/app"
"android.googlesource.com/platform/tools/gpu/framework/device"
"android.googlesource.com/platform/tools/gpu/framework/file"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/gapid/pkgdata"
)
var (
targetDevice string
duration time.Duration
output string
localPort int
localApp file.Path
observeFrames uint
observeDraws uint
disablePCS bool
recordErrors bool
clearCache bool
inputFile string
recordInputs bool
replayInputs bool
useLDPreload bool
apkPath file.Path
interceptorPath file.Path
gapiiPath file.Path
vulkanLayerPath file.Path
)
func init() {
verb := &app.Verb{
Name: "trace",
ShortHelp: "Captures a gfx trace from an application",
Run: doTrace,
}
verb.Flags.StringVar(&targetDevice, "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.Var(&localApp, "local-app", "a local program to trace")
verb.Flags.UintVar(&observeFrames, "observe-frames", 0, "capture the framebuffer every n frames (0 to disable)")
verb.Flags.UintVar(&observeDraws, "observe-draws", 0, "capture the framebuffer every n draws (0 to disable)")
verb.Flags.BoolVar(&disablePCS, "disable-pcs", false, "disable pre-compiled shaders")
verb.Flags.BoolVar(&recordErrors, "record-errors", false, "record device error state")
verb.Flags.BoolVar(&clearCache, "clear-cache", 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")
verb.Flags.Var(&apkPath, "apk", "the path to an apk to install")
verb.Flags.Var(&interceptorPath, "interceptor", "the path to the interceptor library")
verb.Flags.Var(&gapiiPath, "gapii", "the path to the gapii library")
verb.Flags.Var(&vulkanLayerPath, "vulkan-layer", "the path to the vulkan trace layer library")
app.AddVerb(verb)
}
func doTrace(ctx log.Context, flags flag.FlagSet) error {
options := gapii.Options{
ObserveFrameFrequency: uint32(observeFrames),
ObserveDrawFrequency: uint32(observeDraws),
APK: apkPath,
Interceptor: interceptorPath,
Gapii: gapiiPath,
VkLayer: vulkanLayerPath,
}
if disablePCS {
options.Flags |= gapii.DisablePrecompiledShaders
}
if recordErrors {
options.Flags |= gapii.RecordErrorState
}
if !localApp.IsEmpty() {
// Run the local application with VK_LAYER_PATH, VK_INSTANCE_LAYERS,
// VK_DEVICE_LAYERS and LD_PRELOAD set to correct values to load the spy
// layer.
localAbi := device.UnknownABI
switch runtime.GOOS {
case "linux":
localAbi = device.LinuxX86_64
default:
return fmt.Errorf("Unsupported OS for local tracing")
}
var err error
if vulkanLayerPath.IsEmpty() {
vulkanLayerPath, err = pkgdata.File(ctx, localAbi, "LibVkLayerGraphicsSpy.so")
}
if err != nil {
return err
}
if gapiiPath.IsEmpty() {
// TODO (qining): library name may change for different OS/ABI
gapiiPath, err = pkgdata.File(ctx, localAbi, "libgapii.so")
}
if err != nil {
return err
}
libGapiiDirStr := filepath.Dir(gapiiPath.String())
libGapiiPathStr := filepath.Join(libGapiiDirStr, "libgapii.so")
vulkanLayerDirStr := filepath.Dir(vulkanLayerPath.String())
os.Setenv("VK_LAYER_PATH", vulkanLayerDirStr)
os.Setenv("VK_INSTANCE_LAYERS", "VkGraphicsSpy")
os.Setenv("VK_DEVICE_LAYERS", "VkGraphicsSpy")
os.Setenv("LD_PRELOAD", libGapiiPathStr)
boundPort, err := process.Start(ctx, localApp.String())
if err != nil {
return err
}
if localPort == 0 {
localPort = boundPort
}
}
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)
}
// gapiiABI returns the ABI-flavor of GAPII to use given the device and package.
func gapiiABI(ctx log.Context, d bind.Device, info *apk.Information) device.ABI {
for _, preferred := range d.Info().Configuration.ABIs {
for _, abi := range info.ABI {
if device.SameABI(preferred, *abi) {
return preferred
}
}
}
return d.ABI() // Meh.
}
func captureADB(ctx log.Context, flags flag.FlagSet, options gapii.Options, useLDPreload bool) error {
d, err := getADBDevice(ctx, targetDevice)
if err != nil {
return err
}
var a *android.Action
if !options.APK.IsEmpty() {
ctx.V("Source", options.APK).Print("Installing APK")
data, err := ioutil.ReadFile(options.APK.System())
if err != nil {
return ctx.WrapError(err, "Read APK")
}
info, err := apk.Analyze(ctx, data)
if err != nil {
return ctx.WrapError(err, "Analyse APK")
}
if err := d.InstallAPK(ctx, options.APK.System(), true, true); err != nil {
return ctx.WrapError(err, "Install APK")
}
pkg := &android.InstalledPackage{
Name: info.Name,
Device: d,
ABI: gapiiABI(ctx, d, info),
Debuggable: info.Debuggable,
}
defer func() {
ctx.Notice().V("apk", options.APK).Log("Uninstall APK")
pkg.Uninstall(ctx)
}()
a = &android.Action{
Name: info.Action,
Package: pkg,
Activity: info.Activity,
}
} else {
if flags.NArg() != 1 {
app.Usage(ctx, "Invalid number of arguments. Expected 1, got %d", flags.NArg())
return nil
}
activity := flags.Arg(0)
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
}
libraries := gapii.NewLibraries()
libraries.Set(gapii.LibInterceptor, options.Interceptor)
libraries.Set(gapii.LibGapii, options.Gapii)
libraries.Set(gapii.LibVkLayer, options.VkLayer)
if err := libraries.Find(ctx, a); err != nil {
return err
}
port, cleanup, err := start(ctx, libraries, 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, int(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 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
}