blob: 430d61a2ebea8aee63702c4540d38f93a5565b7a [file] [log] [blame]
// Copyright (C) 2015 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/log"
)
func closed(s chan struct{}) bool {
select {
case <-s:
return true
default:
return false
}
}
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(logger log.Logger, port int, w io.Writer, stop chan struct{}) (int64, error) {
if closed(stop) {
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()
var count, nextSize siSize
startTime := time.Now()
nextTime := startTime
for {
if closed(stop) {
log.Infof(logger, "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)
if err == io.EOF {
log.Infof(logger, "EOF: %v", count)
break
}
if err != nil {
err, isnet := err.(net.Error)
if !isnet || (!err.Temporary() && !err.Timeout()) {
log.Infof(logger, "Connection error: %v", err)
return int64(count), err
}
}
if count > nextSize || now.After(nextTime) {
nextSize = count + sizeGap
nextTime = now.Add(timeGap)
delta := time.Duration(int64(now.Sub(startTime)/time.Millisecond)) * time.Millisecond
log.Infof(logger, "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.
// It copies the capture into the supplied writer.
func Capture(logger log.Logger, port int, w io.Writer, stop chan struct{}) (int64, error) {
log.Infof(logger, "Waiting for connection to localhost:%d...", port)
for {
count, err := capture(logger, port, w, stop)
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 <-stop:
log.Infof(logger, "Aborted.")
return 0, nil
case <-time.After(500 * time.Millisecond):
log.Infof(logger, "Retry...")
}
}
}