blob: 67ea54de48958efd9a31b24804079ba2f6a715b2 [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 gapii
import (
"fmt"
"io"
"math"
"net"
"time"
"android.googlesource.com/platform/tools/gpu/framework/binary/cyclic"
"android.googlesource.com/platform/tools/gpu/framework/binary/schema"
"android.googlesource.com/platform/tools/gpu/framework/binary/vle"
"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/atom"
)
// CaptureTag is the trace file header tag.
const CaptureTag = "GapiiTraceFile_V1.1"
// Flags is a bit-field of flags to use when creating a capture.
type Flags uint32
const (
// DisablePrecompiledShaders fakes no support for PCS, forcing the app to
// share shader source.
DisablePrecompiledShaders Flags = 0x00000001
// RecordErrorState queries the driver error state after each all and stores
// errors as extras.
RecordErrorState Flags = 0x10000000
)
// Options to use when creating a capture.
type Options struct {
// If non-zero, then a framebuffer-observation will be made after every n end-of-frames.
ObserveFrameFrequency uint32
// If non-zero, then a framebuffer-observation will be made after every n draw calls.
ObserveDrawFrequency uint32
// Combination of FlagXX bits.
Flags Flags
// APK is an apk to install before tracing
APK file.Path
// Interceptor is the path to an interceptor.so to use
Interceptor file.Path
// Gapii is the path to an libgapii.so to use
Gapii file.Path
// VkLayer is the path to the vulkan tracing layer to use
VkLayer file.Path
}
const sizeGap = 1024 * 1024 * 5
const timeGap = time.Second
type siSize int64
var formats = []string{
"%.0fB",
"%.2fKB",
"%.2fMB",
"%.2fGB",
"%.2fTB",
"%.2fPB",
"%.2fEB",
}
func (s siSize) String() string {
if s == 0 {
return "0.0B"
}
size := float64(s)
e := math.Floor(math.Log(size) / math.Log(1000))
f := formats[int(e)]
v := math.Floor(size/math.Pow(1000, e)*10+0.5) / 10
return fmt.Sprintf(f, v)
}
func capture(ctx log.Context, port int, w io.Writer, o Options) (int64, error) {
if task.Stopped(ctx) {
return 0, nil
}
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {
return 0, nil // Treat failure-to-connect as target-not-ready instead of an error.
}
defer conn.Close()
if err := sendHeader(conn, o); err != nil {
return 0, ctx.WrapError(err, "Header send failed")
}
var count, nextSize siSize
startTime := time.Now()
nextTime := startTime
for {
if task.Stopped(ctx) {
ctx.Printf("Stop: %v", count)
break
}
now := time.Now()
conn.SetReadDeadline(now.Add(time.Millisecond * 100)) // Allow for stop event and UI refreshes.
n, err := io.CopyN(w, conn, 1024*64)
count += siSize(n)
switch {
case err == io.EOF:
// End of stream. End.
ctx.Printf("EOF: %v", count)
return int64(count), nil
case err != nil && count > 0:
err, isnet := err.(net.Error)
if !isnet || (!err.Temporary() && !err.Timeout()) {
ctx.Info().Fail(err, "Connection error")
// Got an error mid-stream terminate.
return int64(count), err
}
case err != nil && count == 0:
// Got an error without receiving a byte of data.
// Treat failure-to-connect as target-not-ready instead of an error.
return 0, nil
}
if count > nextSize || now.After(nextTime) {
nextSize = count + sizeGap
nextTime = now.Add(timeGap)
delta := time.Duration(int64(now.Sub(startTime)/time.Millisecond)) * time.Millisecond
ctx.Printf("Capturing: %v in %v", count, delta)
}
}
return int64(count), nil
}
// Capture opens up the specified port and then waits for a capture to be
// delivered using the specified capture options.
// It copies the capture into the supplied writer.
func Capture(ctx log.Context, port int, w io.Writer, options Options) (int64, error) {
ctx.Printf("Waiting for connection to localhost:%d...", port)
for {
count, err := capture(ctx, port, w, options)
if err != nil {
return count, err
}
if count != 0 {
return count, nil
}
// ADB has an annoying tendancy to insta-close forwarded sockets when
// there's no application waiting for the connection. Treat this as
// another waiting-for-connection case.
select {
case <-task.ShouldStop(ctx):
ctx.Print("Aborted.")
return 0, nil
case <-time.After(500 * time.Millisecond):
ctx.Print("Retry...")
}
}
}
// ReadCapture converts the contents of a capture stream to an atom list.
func ReadCapture(ctx log.Context, in io.Reader) (*atom.List, error) {
list := atom.NewList()
d := cyclic.Decoder(vle.Reader(in))
tag := d.String()
if d.Error() != nil {
return list, d.Error()
}
if tag != CaptureTag {
return list, fmt.Errorf("Invalid capture tag '%s'", tag)
}
for {
obj := d.Variant()
if d.Error() != nil {
if d.Error() != io.EOF {
ctx.Warning().I("len", len(list.Atoms)).Fail(d.Error(), "Decode of capture errored")
if len(list.Atoms) > 0 {
a := list.Atoms[len(list.Atoms)-1]
ctx.Notice().V("atom", a).Log("Last atom successfully decoded")
}
}
break
}
switch obj := obj.(type) {
case atom.Atom:
list.Atoms = append(list.Atoms, obj)
case *schema.Object:
a, err := atom.Wrap(obj)
if err != nil {
return list, err
}
list.Atoms = append(list.Atoms, a)
default:
return list, fmt.Errorf("Expected atom, got '%T' after decoding %d atoms", obj, len(list.Atoms))
}
}
return list, nil
}