blob: 5ed6fe744fc9a5a8bd9a005bdbc0c9e9e68bfd1a [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 (
"encoding/json"
"fmt"
"math/rand"
"os"
"time"
"android.googlesource.com/platform/tools/gpu/client/gapis"
"android.googlesource.com/platform/tools/gpu/framework/benchmark"
"android.googlesource.com/platform/tools/gpu/framework/binary/schema"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/stringtable"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/gapid/atom"
"android.googlesource.com/platform/tools/gpu/gapid/auth"
"android.googlesource.com/platform/tools/gpu/gapid/service"
"android.googlesource.com/platform/tools/gpu/gapid/service/path"
img "android.googlesource.com/platform/tools/gpu/gapid/image"
)
const (
gapisLink = "gapis"
traceLink = "trace"
)
type session struct {
ctx log.Context
bench *Benchmark
tracefile string
runIdx int
client service.Client
capture *path.Capture
device *path.Device
atoms []atom.Atom
schema schema.Message
features []string
stringTables []*stringtable.StringTable
resourceBundles interface{}
report interface{}
contexts interface{}
hierarchies interface{}
}
func fullRun(ctx log.Context, bench *Benchmark) (err error) {
// Update Gapis link.
g, err := newGapisLink(bench, bench.Input.Gapis.Get().Bundle)
if err != nil {
return err
}
bench.Input.Gapis = g
bench.Links[traceLink] = bench.Input.Trace
bench.Links[gapisLink] = bench.Input.Gapis
bench.Meta.HostName, _ = os.Hostname()
traceFile, isTemp, err := bench.Input.Trace.Get().DiskFile()
if isTemp {
defer os.Remove(traceFile)
}
bench.Meta.DateStarted = time.Now()
for runIdx := 0; runIdx < bench.Input.Runs; runIdx++ {
ctx.Info().Logf("Benchmark run: %d/%d", (runIdx + 1), bench.Input.Runs)
if err := singleRun(ctx, bench, runIdx, traceFile); err != nil {
return ctx.WrapError(err, "singleRun")
}
}
bench.TotalTimeTaken.Set(time.Since(bench.Meta.DateStarted))
fit := bench.Samples.IndexedMultisamples().Analyse(
func(m *Multisample) time.Duration { return m.Median() })
if fit != nil {
bench.Fit = fit.String()
} else {
bench.Fit = ""
}
return err
}
func singleRun(ctx log.Context, bench *Benchmark, runIdx int, tracefile string) error {
s := &session{ctx: ctx, bench: bench, tracefile: tracefile, runIdx: runIdx}
if err := s.gapisConnect(); err != nil {
return err
}
defer s.client.Close(ctx)
start := time.Now()
var actions []func() error
switch bench.Input.BenchmarkType {
case "state", "frames":
actions = []func() error{
s.getDevices,
s.loadCapture,
s.getAtoms,
s.maybeBeginProfile,
s.grabSamples,
s.maybeSaveProfileData,
}
case "startup":
actions = []func() error{
s.maybeBeginProfile,
s.getSchema,
s.getFeatures,
s.getStringTables,
s.getDevices,
s.loadCapture,
s.getAtoms,
func() error {
return s.get("ResourceBundles", s.capture.ResourceBundles(), &(s.resourceBundles))
},
func() error {
return s.get("Report", s.capture.Report(nil), &(s.report))
},
func() error {
return s.get("Contexts", s.capture.Contexts(), &(s.contexts))
},
func() error {
return s.get("Hierarchies", s.capture.Hierarchies(), &(s.hierarchies))
},
s.maybeSaveProfileData,
}
default:
return fmt.Errorf("Invalid benchmark type: %s", bench.Input.BenchmarkType)
}
for _, action := range actions {
if err := action(); err != nil {
return err
}
}
s.bench.Metric("Actions", time.Since(start))
// TODO(valbulescu): Allow averaging counter values.
if runIdx == 0 {
counterData, err := s.client.GetPerformanceCounters(ctx)
if err != nil {
return err
}
bench.Counters = benchmark.NewCounters()
if err = json.Unmarshal(counterData, bench.Counters); err != nil {
return err
}
}
return nil
}
func (s *session) maybeBeginProfile() error {
if s.bench.Input.EnableCpuProfile {
return s.client.BeginCPUProfile(s.ctx)
}
return nil
}
func (s *session) maybeSaveProfileData() error {
if s.bench.Input.EnableCpuProfile {
data, err := s.client.EndCPUProfile(s.ctx)
if err != nil {
return err
}
if err := s.saveProfileDataEntry(data, "cpu"); err != nil {
return err
}
}
if s.bench.Input.EnableHeapProfile {
data, err := s.client.GetProfile(s.ctx, "heap", 0)
if err != nil {
return err
}
if err := s.saveProfileDataEntry(data, "heap"); err != nil {
return err
}
}
return nil
}
func (s *session) saveProfileDataEntry(data []byte, key string) error {
link, err := s.bench.Root().NewLink(&DataEntry{
DataSource: ByteSliceDataSource(data),
Name: fmt.Sprintf("%s/%s/%d.pprof", s.bench.Input.Name, key, s.runIdx),
Bundle: true,
})
if err != nil {
return err
}
s.bench.Links[fmt.Sprintf("%s/%d", key, s.runIdx)] = link
return nil
}
type sampleGrabber func(log.Context, *session, *path.Atom) error
func (s *session) gapisConnect() error {
s.ctx.Info().Logf("Connecting to GAPIS...")
start := time.Now()
gapis, _, err := gapis.Connect(s.ctx.Severity(log.InfoLevel), 0, auth.GenToken())
if err != nil {
return fmt.Errorf("Failed to connect to the GAPIS server: %v", err)
}
s.bench.Metric("Connect", time.Since(start))
s.client = gapis.(service.Client)
return nil
}
func (s *session) getDevices() error {
s.ctx.Info().Logf("Getting devices...")
start := time.Now()
devices, err := s.client.GetDevices(s.ctx)
if err != nil {
return s.ctx.WrapError(err, "GetDevices")
}
s.bench.Metric("GetDevices", time.Since(start))
if len(devices) != 0 {
s.device = devices[0]
}
return nil
}
func (s *session) getSchema() error {
s.ctx.Info().Logf("Getting schema...")
start := time.Now()
schema, err := s.client.GetSchema(s.ctx)
if err != nil {
return s.ctx.WrapError(err, "GetSchema")
}
s.bench.Metric("GetSchema", time.Since(start))
s.schema = schema
return nil
}
func (s *session) getFeatures() error {
s.ctx.Info().Logf("Getting features...")
start := time.Now()
features, err := s.client.GetFeatures(s.ctx)
if err != nil {
return s.ctx.WrapError(err, "GetFeatures")
}
s.bench.Metric("GetFeatures", time.Since(start))
s.features = features
return nil
}
func (s *session) getStringTables() error {
s.ctx.Info().Logf("Getting string tables...")
start := time.Now()
stringTableInfos, err := s.client.GetAvailableStringTables(s.ctx)
if err != nil {
return s.ctx.WrapError(err, "GetAvailableStringTables")
}
s.bench.Metric("GetAvailableStringTables", time.Since(start))
s.stringTables = make([]*stringtable.StringTable, len(stringTableInfos))
for i := range s.stringTables {
stringTable, err := s.client.GetStringTable(s.ctx, stringTableInfos[i])
if err != nil {
return err
}
s.stringTables[i] = stringTable
}
s.bench.Metric("GetStringTables", time.Since(start))
return nil
}
func (s *session) loadCapture() error {
s.ctx.Info().Logf("Loading capture file %s...", s.tracefile)
start := time.Now()
capture, err := s.client.LoadCapture(s.ctx, s.tracefile)
if err != nil {
return s.ctx.WrapError(err, "Failed to load the capture file")
}
s.bench.Metric("LoadCapture", time.Since(start))
s.capture = capture
return nil
}
func (s *session) get(what string, p path.Path, dest *interface{}) error {
metric := fmt.Sprintf("Get%s", what)
s.ctx.Info().Logf("Getting: %s...", what)
start := time.Now()
var err error
*dest, err = s.client.Get(s.ctx, p)
if err != nil {
return s.ctx.WrapError(err, metric)
}
s.bench.Metric(metric, time.Since(start))
return nil
}
func (s *session) getAtoms() error {
var result interface{}
if err := s.get("Atoms", s.capture.Atoms(), &result); err != nil {
return err
}
s.atoms = result.(*atom.List).Atoms
return nil
}
func getAtomIndicesAndSampleGrabber(bench *Benchmark, session *session) (err error, arr indices, grab sampleGrabber) {
arr = newConsecutiveIndices(len(session.atoms))
switch bench.Input.SampleOrder {
case "ordered":
case "random":
arr = arr.randomize()
case "reverse":
arr = arr.reverse()
default:
return fmt.Errorf("Invalid sample order: %s", bench.Input.SampleOrder), indices{}, nil
}
switch bench.Input.BenchmarkType {
case "frames":
arr, grab = arr.filter(func(idx int) bool {
return session.atoms[idx].AtomFlags().IsEndOfFrame()
}), getFrame
case "state":
grab = getStateAfter
default:
return fmt.Errorf("Invalid benchmark type: %s", bench.Input.BenchmarkType), indices{}, nil
}
arr = arr.take(bench.Input.MaxSamples)
return nil, arr, grab
}
func (s *session) grabSamples() error {
ctx := s.ctx
if s.bench.Input.Timeout > 0 {
ctx, _ = task.WithTimeout(ctx, s.bench.Input.Timeout)
}
rand.Seed(s.bench.Input.Seed)
err, as, grabSample := getAtomIndicesAndSampleGrabber(s.bench, s)
if err != nil {
return err
}
ctx.Info().Logf("Getting samples...")
start := time.Now()
for i, index := range as {
ctx.Debug().Logf("Index %d [%d/%d]", index, (i + 1), len(as))
if task.Stopped(ctx) {
break
}
start := time.Now()
if err := grabSample(ctx, s, s.capture.Atoms().Index(uint64(index))); err != nil {
return err
}
s.bench.Samples.Add(int64(index), time.Since(start))
}
s.bench.Metric("GrabSamples", time.Since(start))
ctx.Info().Logf("Benchmark complete.")
return nil
}
func getStateAfter(ctx log.Context, session *session, atom *path.Atom) error {
_, err := session.client.Get(ctx, atom.StateAfter())
return err
}
func getFrame(ctx log.Context, session *session, atom *path.Atom) error {
settings := service.RenderSettings{
MaxWidth: uint32(session.bench.Input.MaxFrameWidth),
MaxHeight: uint32(session.bench.Input.MaxFrameHeight),
}
imgInfoPath, err := session.client.GetFramebufferColor(ctx, session.device, atom, settings)
if err != nil {
return err
}
imgInfo, err := session.client.Get(ctx, imgInfoPath)
if err != nil {
return err
}
ii := imgInfo.(*img.Info)
_, err = session.client.Get(ctx, ii.Data)
if err != nil {
return err
}
if ii.Width == 0 || ii.Height == 0 {
return fmt.Errorf("Framebuffer at atom %d was %x x %x", atom.Index, ii.Width, ii.Height)
}
return nil
}