From dac89d8744716689a7f5f9ce506e2b210f443940 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Thu, 23 Aug 2018 17:55:11 -0400 Subject: [PATCH 1/2] Add export_replay for standalone replay with gapir. --- cmd/gapir/cc/main.cpp | 24 +++++- cmd/gapit/BUILD.bazel | 1 + cmd/gapit/export_replay.go | 68 +++++++++++++++ cmd/gapit/flags.go | 5 ++ core/archive/BUILD.bazel | 25 ++++++ core/archive/archive.go | 45 ++++++++++ core/cc/archive.cpp | 14 ++++ core/cc/archive.h | 12 +++ gapir/cc/replay_archive.cpp | 50 ++++++++++++ gapir/cc/replay_archive.h | 51 ++++++++++++ gapir/cc/resource_cache.cpp | 3 + gapis/api/vulkan/replay.go | 5 ++ gapis/client/client.go | 15 ++++ gapis/replay/BUILD.bazel | 4 + gapis/replay/export_replay.go | 150 ++++++++++++++++++++++++++++++++++ gapis/server/BUILD.bazel | 1 + gapis/server/grpc.go | 9 ++ gapis/server/server.go | 9 ++ gapis/service/service.go | 3 + gapis/service/service.proto | 13 +++ 20 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 cmd/gapit/export_replay.go create mode 100644 core/archive/BUILD.bazel create mode 100644 core/archive/archive.go create mode 100644 gapir/cc/replay_archive.cpp create mode 100644 gapir/cc/replay_archive.h create mode 100644 gapis/replay/export_replay.go diff --git a/cmd/gapir/cc/main.cpp b/cmd/gapir/cc/main.cpp index ee0c58a259..4b7c60573b 100644 --- a/cmd/gapir/cc/main.cpp +++ b/cmd/gapir/cc/main.cpp @@ -17,6 +17,7 @@ #include "gapir/cc/context.h" #include "gapir/cc/crash_uploader.h" #include "gapir/cc/memory_manager.h" +#include "gapir/cc/replay_archive.h" #include "gapir/cc/replay_connection.h" #include "gapir/cc/resource_disk_cache.h" #include "gapir/cc/resource_in_memory_cache.h" @@ -210,9 +211,12 @@ int main(int argc, const char* argv[]) { const char* portArgStr = "0"; const char* authTokenFile = nullptr; int idleTimeoutSec = 0; + const char* replayArchive = nullptr; for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--auth-token-file") == 0) { + if (strcmp(argv[i], "--replay-archive") == 0) { + replayArchive = argv[++i]; + } else if (strcmp(argv[i], "--auth-token-file") == 0) { if (i + 1 >= argc) { GAPID_FATAL("Usage: --auth-token-file "); } @@ -283,6 +287,24 @@ int main(int argc, const char* argv[]) { core::Debugger::waitForAttach(); } + if (replayArchive) { + core::CrashHandler crashHandler; + GAPID_LOGGER_INIT(logLevel, "gapir", logPath); + MemoryManager memoryManager(memorySizes); + std::string payloadPath = std::string(replayArchive) + "/payload.bin"; + gapir::ReplayArchive conn(payloadPath); + std::unique_ptr resourceProvider = + ResourceDiskCache::create(nullptr, replayArchive); + std::unique_ptr context = Context::create( + &conn, crashHandler, resourceProvider.get(), &memoryManager); + + GAPID_INFO("Replay started"); + bool ok = context->interpret(); + GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; + } + core::CrashHandler crashHandler; GAPID_LOGGER_INIT(logLevel, "gapir", logPath); diff --git a/cmd/gapit/BUILD.bazel b/cmd/gapit/BUILD.bazel index e55950a540..1b191d1cc1 100644 --- a/cmd/gapit/BUILD.bazel +++ b/cmd/gapit/BUILD.bazel @@ -24,6 +24,7 @@ go_library( "dump.go", "dump_pipeline.go", "dump_shaders.go", + "export_replay.go", "flags.go", "inputs.go", "main.go", diff --git a/cmd/gapit/export_replay.go b/cmd/gapit/export_replay.go new file mode 100644 index 0000000000..a782451654 --- /dev/null +++ b/cmd/gapit/export_replay.go @@ -0,0 +1,68 @@ +// Copyright (C) 2017 Google Inc. +// +// 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 ( + "context" + "flag" + "path/filepath" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" +) + +type exportReplayVerb struct{ ExportReplayFlags } + +func init() { + verb := &exportReplayVerb{} + app.AddVerb(&app.Verb{ + Name: "export_replay", + ShortHelp: "Export replay vm instruction and assets.", + Action: verb, + }) +} + +func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error { + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil + } + + capture, err := filepath.Abs(flags.Arg(0)) + if err != nil { + log.Errf(ctx, err, "Could not find capture file: %v", flags.Arg(0)) + } + + client, err := getGapis(ctx, verb.Gapis, verb.Gapir) + if err != nil { + return log.Err(ctx, err, "Failed to connect to the GAPIS server") + } + defer client.Close() + + capturePath, err := client.LoadCapture(ctx, capture) + if err != nil { + return log.Err(ctx, err, "Failed to load the capture file") + } + + device, err := getDevice(ctx, client, capturePath, verb.Gapir) + if err != nil { + return err + } + + if err := client.ExportReplay(ctx, capturePath, device, verb.Out); err != nil { + return log.Err(ctx, err, "Failed to export replay") + } + return nil +} diff --git a/cmd/gapit/flags.go b/cmd/gapit/flags.go index 5671cecd24..3f5c56d506 100644 --- a/cmd/gapit/flags.go +++ b/cmd/gapit/flags.go @@ -110,6 +110,11 @@ type ( DisplayToSurface bool `help:"display the frames rendered in the replay back to the surface"` CommandFilterFlags } + ExportReplayFlags struct { + Gapis GapisFlags + Gapir GapirFlags + Out string `help:"output directory for commands and assets"` + } VideoFlags struct { Gapis GapisFlags Gapir GapirFlags diff --git a/core/archive/BUILD.bazel b/core/archive/BUILD.bazel new file mode 100644 index 0000000000..f2e2d08f79 --- /dev/null +++ b/core/archive/BUILD.bazel @@ -0,0 +1,25 @@ +# Copyright (C) 2018 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["archive.go"], + cdeps = ["//core/cc"], + cgo = True, + clinkopts = [], # keep + importpath = "github.com/google/gapid/core/archive", + visibility = ["//visibility:public"], +) diff --git a/core/archive/archive.go b/core/archive/archive.go new file mode 100644 index 0000000000..70e101239f --- /dev/null +++ b/core/archive/archive.go @@ -0,0 +1,45 @@ +// Copyright (C) 2018 Google Inc. +// +// 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 archive + +/* +#include +#include "core/cc/archive.h" +*/ +import "C" +import "unsafe" + +// Archive contains assets. +type Archive = *C.archive + +// New creates an archive. +func New(name string) Archive { + cstr := C.CString(name) + defer C.free(unsafe.Pointer(cstr)) + return C.archive_create(cstr) +} + +// Dispose flush and close the underlying archive. +func (a Archive) Dispose() { + C.archive_destroy(a) +} + +// Write writes key-value pair to the archive. +func (a Archive) Write(key string, value []byte) bool { + cstr := C.CString(key) + defer C.free(unsafe.Pointer(cstr)) + csize := C.size_t(len(value)) + return C.archive_write(a, cstr, unsafe.Pointer(&value[0]), csize) != 0 +} diff --git a/core/cc/archive.cpp b/core/cc/archive.cpp index 2665760dd4..a09a8f3fa2 100644 --- a/core/cc/archive.cpp +++ b/core/cc/archive.cpp @@ -129,3 +129,17 @@ bool Archive::write(const std::string& id, const void* buffer, uint32_t size) { } } // namespace core + +extern "C" { + +archive* archive_create(const char* archiveName) { + return reinterpret_cast(new core::Archive(archiveName)); +} + +void archive_destroy(archive* a) { delete reinterpret_cast(a); } + +int archive_write(archive* a, const char* id, const void* buffer, size_t size) { + return reinterpret_cast(a)->write(id, buffer, size) ? 1 : 0; +} + +} // extern "C" diff --git a/core/cc/archive.h b/core/cc/archive.h index e1061b7488..10e845747a 100644 --- a/core/cc/archive.h +++ b/core/cc/archive.h @@ -17,6 +17,8 @@ #ifndef CORE_ARCHIVE_H #define CORE_ARCHIVE_H +#ifdef __cplusplus + #include "id.h" #include @@ -57,4 +59,14 @@ class Archive { } // namespace core +extern "C" { +#endif +typedef struct archive_t archive; +archive* archive_create(const char* archiveName); +void archive_destroy(archive* a); +int archive_write(archive* a, const char* id, const void* buffer, size_t size); +#ifdef __cplusplus +} // extern "C" +#endif + #endif // CORE_ARCHIVE_H diff --git a/gapir/cc/replay_archive.cpp b/gapir/cc/replay_archive.cpp new file mode 100644 index 0000000000..13b3ea248f --- /dev/null +++ b/gapir/cc/replay_archive.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 Google Inc. + * + * 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. + */ + +#include "replay_archive.h" + +#include +#include +#include + +#include "gapir/replay_service/service.grpc.pb.h" + +namespace gapir { + +std::unique_ptr ReplayArchive::getPayload() { + std::fstream input(mFileprefix, std::ios::in | std::ios::binary); + std::unique_ptr payload(new replay_service::Payload); + payload->ParseFromIstream(&input); + return std::unique_ptr(new Payload(std::move(payload))); +} + +std::unique_ptr ReplayArchive::getResources( + std::unique_ptr req) { + return nullptr; +} +bool ReplayArchive::sendReplayFinished() { return true; } +bool ReplayArchive::sendCrashDump(const std::string& filepath, + const void* crash_data, uint32_t crash_size) { + return true; +} +bool ReplayArchive::sendPostData(std::unique_ptr posts) { return true; } +bool ReplayArchive::sendNotification(uint64_t id, uint32_t severity, + uint32_t api_index, uint64_t label, + const std::string& msg, const void* data, + uint32_t data_size) { + return true; +} +} // namespace gapir diff --git a/gapir/cc/replay_archive.h b/gapir/cc/replay_archive.h new file mode 100644 index 0000000000..8d76b5315d --- /dev/null +++ b/gapir/cc/replay_archive.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Google Inc. + * + * 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. + */ + +#ifndef GAPIR_REPLAY_ARCHIVE_H +#define GAPIR_REPLAY_ARCHIVE_H + +#include "core/cc/archive.h" +#include "replay_connection.h" +namespace gapir { + +// ReplayArchive implements ReplayConnection for exported replays. +class ReplayArchive : public ReplayConnection { + public: + ReplayArchive(const std::string& fileprefix) + : ReplayConnection(nullptr), mFileprefix(fileprefix) {} + // Read payload from disk. + virtual std::unique_ptr getPayload() override; + + // We are reading from disk, so the following methods are not implemented. + virtual std::unique_ptr getResources( + std::unique_ptr req) override; + virtual bool sendReplayFinished() override; + virtual bool sendCrashDump(const std::string& filepath, + const void* crash_data, + uint32_t crash_size) override; + virtual bool sendPostData(std::unique_ptr posts) override; + virtual bool sendNotification(uint64_t id, uint32_t severity, + uint32_t api_index, uint64_t label, + const std::string& msg, const void* data, + uint32_t data_size) override; + + private: + std::string mFileprefix; +}; + +} // namespace gapir + +#endif // GAPIR_REPLAY_ARCHIVE_H diff --git a/gapir/cc/resource_cache.cpp b/gapir/cc/resource_cache.cpp index 3d437aaf16..b45dc145fd 100644 --- a/gapir/cc/resource_cache.cpp +++ b/gapir/cc/resource_cache.cpp @@ -88,6 +88,9 @@ bool ResourceCache::Batch::flush(ResourceCache& cache, ReplayConnection* conn) { if (count == 0) { return true; } + if (!cache.mFallbackProvider) { + return false; + } if (!cache.mFallbackProvider->get(mResources.data(), count, conn, ptr, mSize)) { return false; diff --git a/gapis/api/vulkan/replay.go b/gapis/api/vulkan/replay.go index d3135331da..db9c25ab08 100644 --- a/gapis/api/vulkan/replay.go +++ b/gapis/api/vulkan/replay.go @@ -781,3 +781,8 @@ func (a API) QueryIssues( } return res.([]replay.Issue), nil } + +// ExportReplayRequest returns request type for standalone replay. +func (a API) ExportReplayRequest() replay.Request { + return issuesRequest{} +} diff --git a/gapis/client/client.go b/gapis/client/client.go index fc40f32019..80acaa500e 100644 --- a/gapis/client/client.go +++ b/gapis/client/client.go @@ -283,6 +283,21 @@ func (c *client) SaveCapture(ctx context.Context, capture *path.Capture, path st return nil } +func (c *client) ExportReplay(ctx context.Context, capture *path.Capture, device *path.Device, path string) error { + res, err := c.client.ExportReplay(ctx, &service.ExportReplayRequest{ + Capture: capture, + Path: path, + Device: device, + }) + if err != nil { + return err + } + if err := res.GetError(); err != nil { + return err.Get() + } + return nil +} + func (c *client) GetDevices(ctx context.Context) ([]*path.Device, error) { res, err := c.client.GetDevices(ctx, &service.GetDevicesRequest{}) if err != nil { diff --git a/gapis/replay/BUILD.bazel b/gapis/replay/BUILD.bazel index 92db3fc028..60fd12bd33 100644 --- a/gapis/replay/BUILD.bazel +++ b/gapis/replay/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "custom.go", "doc.go", "events.go", + "export_replay.go", "interfaces.go", "manager.go", "mapping_printer.go", @@ -33,6 +34,7 @@ go_library( "//core/app/analytics:go_default_library", "//core/app/benchmark:go_default_library", "//core/app/status:go_default_library", + "//core/archive:go_default_library", "//core/context/keys:go_default_library", "//core/data/binary:go_default_library", "//core/data/id:go_default_library", @@ -45,6 +47,7 @@ go_library( "//gapis/api/transform:go_default_library", "//gapis/capture:go_default_library", "//gapis/config:go_default_library", + "//gapis/database:go_default_library", "//gapis/memory:go_default_library", "//gapis/replay/builder:go_default_library", "//gapis/replay/executor:go_default_library", @@ -52,5 +55,6 @@ go_library( "//gapis/resolve/initialcmds:go_default_library", "//gapis/service:go_default_library", "//gapis/service/path:go_default_library", + "@com_github_golang_protobuf//proto:go_default_library", ], ) diff --git a/gapis/replay/export_replay.go b/gapis/replay/export_replay.go new file mode 100644 index 0000000000..bfc56869f4 --- /dev/null +++ b/gapis/replay/export_replay.go @@ -0,0 +1,150 @@ +// Copyright (C) 2017 Google Inc. +// +// 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 replay + +import ( + "context" + "io/ioutil" + "os" + gopath "path" + + "github.com/golang/protobuf/proto" + "github.com/google/gapid/core/archive" + "github.com/google/gapid/core/data/id" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/device/bind" + gapir "github.com/google/gapid/gapir/client" + "github.com/google/gapid/gapis/capture" + "github.com/google/gapid/gapis/database" + "github.com/google/gapid/gapis/replay/builder" + "github.com/google/gapid/gapis/resolve/initialcmds" + "github.com/google/gapid/gapis/service/path" +) + +// ExportReplay write replay commands and assets to path. +func ExportReplay(ctx context.Context, pCapture *path.Capture, pDevice *path.Device, outDir string) error { + if pDevice == nil { + return log.Errf(ctx, nil, "Unable to produce replay on unknown device.") + } + + ctx = capture.Put(ctx, pCapture) + c, err := capture.Resolve(ctx) + if err != nil { + return err + } + + // Capture can use multiple APIs. + // Iterate the APIs in use looking for those that support replay generation. + var generator Generator + for _, a := range c.APIs { + if a, ok := a.(Generator); ok { + generator = a + break + } + } + + if generator == nil { + return log.Errf(ctx, nil, "Unable to find replay API.") + } + + d := bind.GetRegistry(ctx).Device(pDevice.ID.ID()) + if d == nil { + return log.Errf(ctx, nil, "Unknown device %v", pDevice.ID.ID()) + } + + // executeCounter.Increment() + + ctx = log.V{ + "capture": pCapture.ID.ID(), + "device": d.Instance().GetName(), + }.Bind(ctx) + + cml := c.Header.ABI.MemoryLayout + ctx = log.V{"capture memory layout": cml}.Bind(ctx) + + deviceABIs := d.Instance().GetConfiguration().GetABIs() + if len(deviceABIs) == 0 { + return log.Err(ctx, nil, "Replay device doesn't list any ABIs") + } + + replayABI := findABI(cml, deviceABIs) + if replayABI == nil { + log.I(ctx, "Replay device does not have a memory layout matching device used to trace") + replayABI = deviceABIs[0] + } + ctx = log.V{"replay target ABI": replayABI}.Bind(ctx) + + b := builder.New(replayABI.MemoryLayout) + + _, ranges, err := initialcmds.InitialCommands(ctx, pCapture) + + generatorReplayTimer.Time(func() { + err = generator.Replay( + ctx, + Intent{pDevice, pCapture}, + Config(&struct{}{}), + []RequestAndResult{{ + Request: Request(generator.(interface{ ExportReplayRequest() Request }).ExportReplayRequest()), + Result: func(val interface{}, err error) {}, + }}, + d.Instance(), + c, + &adapter{ + state: c.NewUninitializedState(ctx, ranges), + builder: b, + }) + }) + + if err != nil { + return log.Err(ctx, err, "Replay returned error") + } + + var payload gapir.Payload + var handlePost builder.PostDataHandler + var handleNotification builder.NotificationHandler + builderBuildTimer.Time(func() { payload, handlePost, handleNotification, err = b.Build(ctx) }) + if err != nil { + return log.Err(ctx, err, "Failed to build replay payload") + } + + err = os.MkdirAll(outDir, os.ModePerm) + if err != nil { + return log.Errf(ctx, err, "Failed to create output directory: %v", outDir) + } + + payloadBytes, err := proto.Marshal(&payload) + if err != nil { + return log.Errf(ctx, err, "Failed to serialize replay payload.") + } + err = ioutil.WriteFile(gopath.Join(outDir, "payload.bin"), payloadBytes, 0644) + + ar := archive.New(gopath.Join(outDir, "resources")) + defer ar.Dispose() + + db := database.Get(ctx) + for _, ri := range payload.Resources { + rID, err := id.Parse(ri.Id) + if err != nil { + return log.Errf(ctx, err, "Failed to parse resource id: %v", ri.Id) + } + obj, err := db.Resolve(ctx, rID) + if err != nil { + return log.Errf(ctx, err, "Failed to parse resource id: %v", ri.Id) + } + ar.Write(ri.Id, obj.([]byte)) + } + + return nil +} diff --git a/gapis/server/BUILD.bazel b/gapis/server/BUILD.bazel index 634662f208..f5f2c69d17 100644 --- a/gapis/server/BUILD.bazel +++ b/gapis/server/BUILD.bazel @@ -40,6 +40,7 @@ go_library( "//gapis/api/all:go_default_library", "//gapis/capture:go_default_library", "//gapis/messages:go_default_library", + "//gapis/replay:go_default_library", "//gapis/replay/devices:go_default_library", "//gapis/resolve:go_default_library", "//gapis/service:go_default_library", diff --git a/gapis/server/grpc.go b/gapis/server/grpc.go index 0223faf2f0..1bbaef1ede 100644 --- a/gapis/server/grpc.go +++ b/gapis/server/grpc.go @@ -379,6 +379,15 @@ func (s *grpcServer) SaveCapture(ctx xctx.Context, req *service.SaveCaptureReque return &service.SaveCaptureResponse{}, nil } +func (s *grpcServer) ExportReplay(ctx xctx.Context, req *service.ExportReplayRequest) (*service.ExportReplayResponse, error) { + defer s.inRPC()() + err := s.handler.ExportReplay(s.bindCtx(ctx), req.Capture, req.Device, req.Path) + if err := service.NewError(err); err != nil { + return &service.ExportReplayResponse{Error: err}, nil + } + return &service.ExportReplayResponse{}, nil +} + func (s *grpcServer) GetDevices(ctx xctx.Context, req *service.GetDevicesRequest) (*service.GetDevicesResponse, error) { defer s.inRPC()() devices, err := s.handler.GetDevices(s.bindCtx(ctx)) diff --git a/gapis/server/server.go b/gapis/server/server.go index e9948ed2cb..54369be973 100644 --- a/gapis/server/server.go +++ b/gapis/server/server.go @@ -40,6 +40,7 @@ import ( "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/capture" "github.com/google/gapid/gapis/messages" + "github.com/google/gapid/gapis/replay" "github.com/google/gapid/gapis/replay/devices" "github.com/google/gapid/gapis/resolve" "github.com/google/gapid/gapis/service" @@ -256,6 +257,14 @@ func (s *server) SaveCapture(ctx context.Context, c *path.Capture, path string) return capture.Export(ctx, c, f) } +func (s *server) ExportReplay(ctx context.Context, c *path.Capture, d *path.Device, path string) error { + ctx = log.Enter(ctx, "ExportReplay") + if !s.enableLocalFiles { + return fmt.Errorf("Server not configured to allow writing of local files") + } + return replay.ExportReplay(ctx, c, d, path) +} + func (s *server) GetDevices(ctx context.Context) ([]*path.Device, error) { ctx = status.Start(ctx, "RPC GetDevices") defer status.Finish(ctx) diff --git a/gapis/service/service.go b/gapis/service/service.go index 83d6a75bd5..dc1b219df4 100644 --- a/gapis/service/service.go +++ b/gapis/service/service.go @@ -78,6 +78,9 @@ type Service interface { // SaveCapture saves the capture to a local file. SaveCapture(ctx context.Context, c *path.Capture, path string) error + // ExportReplay saves replay commands and assets to file. + ExportReplay(ctx context.Context, c *path.Capture, d *path.Device, path string) error + // GetDevices returns the full list of replay devices avaliable to the server. // These include local replay devices and any connected Android devices. // This list may change over time, as devices are connected and disconnected. diff --git a/gapis/service/service.proto b/gapis/service/service.proto index 9519c8c682..cffc9c8b98 100644 --- a/gapis/service/service.proto +++ b/gapis/service/service.proto @@ -278,6 +278,15 @@ message SaveCaptureResponse { Error error = 1; } +message ExportReplayRequest { + path.Capture capture = 1; + string path = 2; + path.Device device = 3; +} +message ExportReplayResponse { + Error error = 1; +} + message GetDevicesRequest { } message GetDevicesResponse { @@ -505,6 +514,10 @@ service Gapid { rpc SaveCapture(SaveCaptureRequest) returns (SaveCaptureResponse) { } + // ExportReplay saves replay commands and assets to file. + rpc ExportReplay(ExportReplayRequest) returns (ExportReplayResponse) { + } + // GetDevices returns the full list of replay devices avaliable to the server. // These include local replay devices and any connected Android devices. // This list may change over time, as devices are connected and disconnected. From bc1247e5c5795ccbf2deb2a3202fca16082c6a9b Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 28 Aug 2018 09:37:00 -0400 Subject: [PATCH 2/2] Fix review comments for replay_archive. --- cmd/gapir/cc/main.cpp | 219 +++++++++++++++++++--------------- core/archive/archive.go | 2 +- gapir/cc/replay_archive.cpp | 6 +- gapis/api/vulkan/replay.go | 18 ++- gapis/replay/export_replay.go | 2 - 5 files changed, 146 insertions(+), 101 deletions(-) diff --git a/cmd/gapir/cc/main.cpp b/cmd/gapir/cc/main.cpp index 4b7c60573b..e08956e27f 100644 --- a/cmd/gapir/cc/main.cpp +++ b/cmd/gapir/cc/main.cpp @@ -201,8 +201,9 @@ void android_main(struct android_app* app) { #else // TARGET_OS == GAPID_OS_ANDROID -// Main function for PC -int main(int argc, const char* argv[]) { +namespace { + +struct Options { int logLevel = LOG_LEVEL; const char* logPath = "logs/gapir.log"; @@ -212,120 +213,124 @@ int main(int argc, const char* argv[]) { const char* authTokenFile = nullptr; int idleTimeoutSec = 0; const char* replayArchive = nullptr; + bool version = false; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--replay-archive") == 0) { - replayArchive = argv[++i]; - } else if (strcmp(argv[i], "--auth-token-file") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --auth-token-file "); - } - authTokenFile = argv[++i]; - } else if (strcmp(argv[i], "--cache") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --cache "); - } - cachePath = argv[++i]; - } else if (strcmp(argv[i], "--port") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --port "); - } - portArgStr = argv[++i]; - } else if (strcmp(argv[i], "--log-level") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --log-level "); - } - switch (argv[++i][0]) { - case 'F': - logLevel = LOG_LEVEL_FATAL; - break; - case 'E': - logLevel = LOG_LEVEL_ERROR; - break; - case 'W': - logLevel = LOG_LEVEL_WARNING; - break; - case 'I': - logLevel = LOG_LEVEL_INFO; - break; - case 'D': - logLevel = LOG_LEVEL_DEBUG; - break; - case 'V': - logLevel = LOG_LEVEL_VERBOSE; - break; - default: + static Options Parse(int argc, const char* argv[]) { + Options opts; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--replay-archive") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --replay-archive "); + } + opts.replayArchive = argv[++i]; + } else if (strcmp(argv[i], "--auth-token-file") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --auth-token-file "); + } + opts.authTokenFile = argv[++i]; + } else if (strcmp(argv[i], "--cache") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --cache "); + } + opts.cachePath = argv[++i]; + } else if (strcmp(argv[i], "--port") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --port "); + } + opts.portArgStr = argv[++i]; + } else if (strcmp(argv[i], "--log-level") == 0) { + if (i + 1 >= argc) { GAPID_FATAL("Usage: --log-level "); + } + switch (argv[++i][0]) { + case 'F': + opts.logLevel = LOG_LEVEL_FATAL; + break; + case 'E': + opts.logLevel = LOG_LEVEL_ERROR; + break; + case 'W': + opts.logLevel = LOG_LEVEL_WARNING; + break; + case 'I': + opts.logLevel = LOG_LEVEL_INFO; + break; + case 'D': + opts.logLevel = LOG_LEVEL_DEBUG; + break; + case 'V': + opts.logLevel = LOG_LEVEL_VERBOSE; + break; + default: + GAPID_FATAL("Usage: --log-level "); + } + } else if (strcmp(argv[i], "--log") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --log "); + } + opts.logPath = argv[++i]; + } else if (strcmp(argv[i], "--idle-timeout-sec") == 0) { + if (i + 1 >= argc) { + GAPID_FATAL("Usage: --idle-timeout-sec "); + } + opts.idleTimeoutSec = atoi(argv[++i]); + } else if (strcmp(argv[i], "--wait-for-debugger") == 0) { + opts.wait_for_debugger = true; + } else if (strcmp(argv[i], "--version") == 0) { + opts.version = true; + } else { + GAPID_FATAL("Unknown argument: %s", argv[i]); } - } else if (strcmp(argv[i], "--log") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --log "); - } - logPath = argv[++i]; - } else if (strcmp(argv[i], "--idle-timeout-sec") == 0) { - if (i + 1 >= argc) { - GAPID_FATAL("Usage: --idle-timeout-sec "); - } - idleTimeoutSec = atoi(argv[++i]); - } else if (strcmp(argv[i], "--wait-for-debugger") == 0) { - wait_for_debugger = true; - } else if (strcmp(argv[i], "--version") == 0) { - printf("GAPIR version " GAPID_VERSION_AND_BUILD "\n"); - return 0; - } else { - GAPID_FATAL("Unknown argument: %s", argv[i]); } + return opts; } +}; -#if TARGET_OS == GAPID_OS_LINUX - // Ignore SIGPIPE so we can log after gapis closes. - signal(SIGPIPE, SIG_IGN); -#endif - - if (wait_for_debugger) { - GAPID_INFO("Waiting for debugger to attach"); - core::Debugger::waitForAttach(); - } +} // namespace - if (replayArchive) { - core::CrashHandler crashHandler; - GAPID_LOGGER_INIT(logLevel, "gapir", logPath); - MemoryManager memoryManager(memorySizes); - std::string payloadPath = std::string(replayArchive) + "/payload.bin"; - gapir::ReplayArchive conn(payloadPath); - std::unique_ptr resourceProvider = - ResourceDiskCache::create(nullptr, replayArchive); - std::unique_ptr context = Context::create( - &conn, crashHandler, resourceProvider.get(), &memoryManager); - - GAPID_INFO("Replay started"); - bool ok = context->interpret(); - GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); - - return ok ? EXIT_SUCCESS : EXIT_FAILURE; - } +static int replayArchive(Options opts) { + // The directory consists an archive(resources.{index,data}) and payload.bin. + core::CrashHandler crashHandler; + GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath); + MemoryManager memoryManager(memorySizes); + std::string payloadPath = std::string(opts.replayArchive) + "/payload.bin"; + gapir::ReplayArchive conn(payloadPath); + std::unique_ptr resourceProvider = + ResourceDiskCache::create(nullptr, opts.replayArchive); + std::unique_ptr context = Context::create( + &conn, crashHandler, resourceProvider.get(), &memoryManager); + + GAPID_INFO("Replay started"); + bool ok = context->interpret(); + GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} +static int startServer(Options opts) { core::CrashHandler crashHandler; - GAPID_LOGGER_INIT(logLevel, "gapir", logPath); + GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath); // Read the auth-token. // Note: This must come before the socket is created as the auth token // file is deleted by GAPIS as soon as the port is written to stdout. std::vector authToken; - if (authTokenFile != nullptr) { - FILE* file = fopen(authTokenFile, "rb"); + if (opts.authTokenFile != nullptr) { + FILE* file = fopen(opts.authTokenFile, "rb"); if (file == nullptr) { - GAPID_FATAL("Unable to open auth-token file: %s", authTokenFile); + GAPID_FATAL("Unable to open auth-token file: %s", opts.authTokenFile); } if (fseek(file, 0, SEEK_END) != 0) { - GAPID_FATAL("Unable to get length of auth-token file: %s", authTokenFile); + GAPID_FATAL("Unable to get length of auth-token file: %s", + opts.authTokenFile); } size_t size = ftell(file); fseek(file, 0, SEEK_SET); authToken.resize(size + 1, 0); if (fread(&authToken[0], 1, size, file) != size) { - GAPID_FATAL("Unable to read auth-token file: %s", authTokenFile); + GAPID_FATAL("Unable to read auth-token file: %s", opts.authTokenFile); } fclose(file); } @@ -334,7 +339,7 @@ int main(int argc, const char* argv[]) { // If the user does not assign a port to use, get a free TCP port from OS. const char local_host_name[] = "127.0.0.1"; - std::string portStr(portArgStr); + std::string portStr(opts.portArgStr); if (portStr == "0") { uint32_t port = SocketConnection::getFreePort(local_host_name); if (port == 0) { @@ -349,7 +354,8 @@ int main(int argc, const char* argv[]) { std::mutex lock; std::unique_ptr server = Setup(uri.c_str(), (authToken.size() > 0) ? authToken.data() : nullptr, - cachePath, idleTimeoutSec, &crashHandler, &memoryManager, &lock); + opts.cachePath, opts.idleTimeoutSec, &crashHandler, &memoryManager, + &lock); // The following message is parsed by launchers to detect the selected port. // DO NOT CHANGE! printf("Bound on port '%s'\n", portStr.c_str()); @@ -361,4 +367,27 @@ int main(int argc, const char* argv[]) { return EXIT_SUCCESS; } +// Main function for PC +int main(int argc, const char* argv[]) { + Options opts = Options::Parse(argc, argv); + +#if TARGET_OS == GAPID_OS_LINUX + // Ignore SIGPIPE so we can log after gapis closes. + signal(SIGPIPE, SIG_IGN); +#endif + + if (opts.wait_for_debugger) { + GAPID_INFO("Waiting for debugger to attach"); + core::Debugger::waitForAttach(); + } + if (opts.version) { + printf("GAPIR version " GAPID_VERSION_AND_BUILD "\n"); + return EXIT_SUCCESS; + } else if (opts.replayArchive) { + return replayArchive(opts); + } else { + return startServer(opts); + } +} + #endif // TARGET_OS == GAPID_OS_ANDROID diff --git a/core/archive/archive.go b/core/archive/archive.go index 70e101239f..c3990d9ba3 100644 --- a/core/archive/archive.go +++ b/core/archive/archive.go @@ -31,7 +31,7 @@ func New(name string) Archive { return C.archive_create(cstr) } -// Dispose flush and close the underlying archive. +// Dispose flushes and closes the underlying archive. func (a Archive) Dispose() { C.archive_destroy(a) } diff --git a/gapir/cc/replay_archive.cpp b/gapir/cc/replay_archive.cpp index 13b3ea248f..11e854a271 100644 --- a/gapir/cc/replay_archive.cpp +++ b/gapir/cc/replay_archive.cpp @@ -15,6 +15,7 @@ */ #include "replay_archive.h" +#include "core/cc/log.h" #include #include @@ -24,20 +25,21 @@ namespace gapir { -std::unique_ptr ReplayArchive::getPayload() { +std::unique_ptr ReplayArchive::getPayload() { std::fstream input(mFileprefix, std::ios::in | std::ios::binary); std::unique_ptr payload(new replay_service::Payload); payload->ParseFromIstream(&input); return std::unique_ptr(new Payload(std::move(payload))); } -std::unique_ptr ReplayArchive::getResources( +std::unique_ptr ReplayArchive::getResources( std::unique_ptr req) { return nullptr; } bool ReplayArchive::sendReplayFinished() { return true; } bool ReplayArchive::sendCrashDump(const std::string& filepath, const void* crash_data, uint32_t crash_size) { + GAPID_INFO("Crash dump saved at: %s", filepath.c_str()); return true; } bool ReplayArchive::sendPostData(std::unique_ptr posts) { return true; } diff --git a/gapis/api/vulkan/replay.go b/gapis/api/vulkan/replay.go index db9c25ab08..22a650eb92 100644 --- a/gapis/api/vulkan/replay.go +++ b/gapis/api/vulkan/replay.go @@ -500,6 +500,10 @@ type issuesRequest struct { displayToSurface bool } +// exportReplayRequest requests full trace to be produced. +type exportReplayRequest struct { +} + func (a API) Replay( ctx context.Context, intent replay.Intent, @@ -590,6 +594,18 @@ func (a API) Replay( doDisplayToSurface = true } + case exportReplayRequest: + // TODO: Implement a transform specifically for export replay + if issues == nil { + n, err := expandCommands(false) + if err != nil { + return err + } + issues = newFindIssues(ctx, capture, n) + } + issues.reportTo(rr.Result) + optimize = false + case framebufferRequest: cfg := cfg.(drawConfig) @@ -784,5 +800,5 @@ func (a API) QueryIssues( // ExportReplayRequest returns request type for standalone replay. func (a API) ExportReplayRequest() replay.Request { - return issuesRequest{} + return exportReplayRequest{} } diff --git a/gapis/replay/export_replay.go b/gapis/replay/export_replay.go index bfc56869f4..8eea452a09 100644 --- a/gapis/replay/export_replay.go +++ b/gapis/replay/export_replay.go @@ -64,8 +64,6 @@ func ExportReplay(ctx context.Context, pCapture *path.Capture, pDevice *path.Dev return log.Errf(ctx, nil, "Unknown device %v", pDevice.ID.ID()) } - // executeCounter.Increment() - ctx = log.V{ "capture": pCapture.ID.ID(), "device": d.Instance().GetName(),