| // 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 |
| } |