Skip to content

Commit

Permalink
Add standalone APK creation to export_replay (gapid2apk) (#2621)
Browse files Browse the repository at this point in the history
The APK is created from the regular GAPID APK by adding replay export
files as assets. GAPIR detects those files and replay from them.
  • Loading branch information
hevrard authored Apr 19, 2019
1 parent 8364fc0 commit fa041f6
Show file tree
Hide file tree
Showing 13 changed files with 820 additions and 85 deletions.
216 changes: 140 additions & 76 deletions cmd/gapir/cc/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
#if TARGET_OS == GAPID_OS_ANDROID
#include <sys/stat.h>
#include "android_native_app_glue.h"
#include "gapir/cc/android/asset_replay_service.h"
#include "gapir/cc/android/asset_resource_cache.h"
#elif TARGET_OS == GAPID_OS_LINUX || TARGET_OS == GAPID_OS_OSX
#include <dirent.h>
#include <ftw.h>
Expand All @@ -55,6 +57,13 @@ using namespace gapir;

namespace {

enum ReplayMode {
kUnknown = 0, // Can't determine replay type from arguments yet.
kConflict, // Impossible combination of command line arguments.
kReplayServer, // Run gapir as a server.
kReplayArchive, // Replay an exported archive.
};

std::vector<uint32_t> memorySizes {
// If we are on desktop, we can try more memory
#if TARGET_OS != GAPID_OS_ANDROID
Expand Down Expand Up @@ -264,12 +273,44 @@ std::unique_ptr<Server> Setup(const char* uri, const char* authToken,
});
}

static int replayArchive(core::CrashHandler* crashHandler,
std::unique_ptr<ResourceCache> resourceCache,
gapir::ReplayService* replayArchiveService) {
// The directory consists an archive(resources.{index,data}) and payload.bin.
MemoryManager memoryManager(memorySizes);
std::unique_ptr<ResourceLoader> resLoader =
CachedResourceLoader::create(resourceCache.get(), nullptr);

std::unique_ptr<Context> context = Context::create(
replayArchiveService, *crashHandler, resLoader.get(), &memoryManager);

if (context->initialize("payload")) {
GAPID_DEBUG("Replay context initialized successfully");
} else {
GAPID_ERROR("Replay context initialization failed");
return EXIT_FAILURE;
}

GAPID_INFO("Replay started");
bool ok = context->interpret();
replayArchiveService->sendReplayFinished();
if (!context->cleanup()) {
GAPID_ERROR("Replay cleanup failed");
return EXIT_FAILURE;
}
GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed");

return ok ? EXIT_SUCCESS : EXIT_FAILURE;
}

} // anonymous namespace

#if TARGET_OS == GAPID_OS_ANDROID

namespace {

const char* kReplayAssetToDetect = "replay_export/resources.index";

template <typename... Args>
jobject jni_call_o(JNIEnv* env, jobject obj, const char* name, const char* sig,
Args&&... args) {
Expand All @@ -287,6 +328,7 @@ int jni_call_i(JNIEnv* env, jobject obj, const char* name, const char* sig,
struct Options {
int idleTimeoutSec = 0;
std::string authToken = "";
ReplayMode mode = kUnknown;

static Options Parse(struct android_app* app) {
Options opts;
Expand All @@ -308,6 +350,20 @@ struct Options {
env->ReleaseStringUTFChars((jstring)token, tmp);
}

opts.mode = kReplayServer;

// Select replay archive mode if replay assets are detected
jobject j_asset_manager =
jni_call_o(env, app->activity->clazz, "getAssets",
"()Landroid/content/res/AssetManager;");
AAssetManager* asset_manager = AAssetManager_fromJava(env, j_asset_manager);
AAsset* asset = AAssetManager_open(asset_manager, kReplayAssetToDetect,
AASSET_MODE_UNKNOWN);
if (asset != nullptr) {
opts.mode = kReplayArchive;
AAsset_close(asset);
}

app->activity->vm->DetachCurrentThread();
return opts;
}
Expand Down Expand Up @@ -341,35 +397,73 @@ void android_process(struct android_app* app, int32_t cmd) {

// Main function for android
void android_main(struct android_app* app) {
MemoryManager memoryManager(memorySizes);
CrashHandler crashHandler;

std::thread waiting_thread;
std::atomic<bool> thread_is_done(false);

// Get the path of the file system socket.
const char* pipe = pipeName();
std::string internal_data_path = std::string(app->activity->internalDataPath);
std::string socket_file_path = internal_data_path + "/" + std::string(pipe);
std::string uri = std::string("unix://") + socket_file_path;

GAPID_INFO(
"Started Graphics API Replay daemon.\n"
"Listening on unix socket '%s'\n"
"Supported ABIs: %s\n",
uri.c_str(), core::supportedABIs());

auto opts = Options::Parse(app);
std::unique_ptr<Server> server = nullptr;
MemoryManager memoryManager(memorySizes);
auto cache = InMemoryResourceCache::create(memoryManager.getTopAddress());
std::mutex lock;
PrewarmData data;
std::unique_ptr<Server> server =
Setup(uri.c_str(), opts.authToken.c_str(), cache.get(),
opts.idleTimeoutSec, &crashHandler, &memoryManager, &data, &lock);
std::atomic<bool> serverIsDone(false);
std::thread waiting_thread([&]() {
server.get()->wait();
serverIsDone = true;
});
if (chmod(socket_file_path.c_str(), S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)) {
GAPID_ERROR("Chmod failed!");

auto opts = Options::Parse(app);

if (opts.mode == kReplayArchive) {
GAPID_INFO("Started Graphics API Replay from archive.");

waiting_thread = std::thread([&]() {
// It's important to use a different JNIEnv as it is a separate thread
JNIEnv* env;
app->activity->vm->AttachCurrentThread(&env, 0);

// Keep a jobject reference in the main thread to prevent garbage
// collection of the asset manager.
// https://developer.android.com/ndk/reference/group/asset#aassetmanager_fromjava
jobject j_asset_manager =
jni_call_o(env, app->activity->clazz, "getAssets",
"()Landroid/content/res/AssetManager;");
AAssetManager* asset_manager =
AAssetManager_fromJava(env, j_asset_manager);

std::unique_ptr<ResourceCache> assetResourceCache =
AssetResourceCache::create(asset_manager);
gapir::AssetReplayService assetReplayService(asset_manager);

replayArchive(&crashHandler, std::move(assetResourceCache),
&assetReplayService);

app->activity->vm->DetachCurrentThread();

thread_is_done = true;
});

} else if (opts.mode == kReplayServer) {
GAPID_INFO(
"Started Graphics API Replay daemon.\n"
"Listening on unix socket '%s'\n"
"Supported ABIs: %s\n",
uri.c_str(), core::supportedABIs());

server =
Setup(uri.c_str(), opts.authToken.c_str(), cache.get(),
opts.idleTimeoutSec, &crashHandler, &memoryManager, &data, &lock);
waiting_thread = std::thread([&]() {
server.get()->wait();
thread_is_done = true;
});
if (chmod(socket_file_path.c_str(),
S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)) {
GAPID_ERROR("Chmod failed!");
}
} else {
GAPID_ERROR("Invalid replay mode");
}

app->onAppCmd = android_process;
Expand All @@ -389,13 +483,15 @@ void android_main(struct android_app* app) {
}
if (app->destroyRequested) {
// Clean up and exit the main loop
server->shutdown();
if (opts.mode == kReplayServer) {
server->shutdown();
}
alive = false;
break;
}
}

if (serverIsDone && !finishing) {
if (thread_is_done && !finishing) {
// Start termination of the app
ANativeActivity_finish(app->activity);

Expand All @@ -411,7 +507,9 @@ void android_main(struct android_app* app) {

// Final clean up
waiting_thread.join();
unlink(socket_file_path.c_str());
if (opts.mode == kReplayServer) {
unlink(socket_file_path.c_str());
}
GAPID_INFO("End of Graphics API Replay");
return;
}
Expand All @@ -429,13 +527,6 @@ struct Options {

int logLevel = LOG_LEVEL;
const char* logPath = "logs/gapir.log";

enum ReplayMode {
kUnknown = 0, // Can't determine replay type from arguments yet.
kConflict, // Impossible combination of command line arguments.
kReplayServer, // Run gapir as a server.
kReplayArchive, // Replay an exported archive.
};
ReplayMode mode = kUnknown;
bool waitForDebugger = false;
const char* cachePath = nullptr;
Expand Down Expand Up @@ -657,7 +748,8 @@ std::unique_ptr<ResourceCache> createCache(
exit(0);
}
}
return std::move(onDiskCache);

return onDiskCache;
#else // TARGET_OS == GAPID_OS_LINUX || TARGET_OS == GAPID_OS_OSX
if (onDiskCacheOpts.enabled) {
GAPID_WARNING(
Expand All @@ -669,47 +761,7 @@ std::unique_ptr<ResourceCache> createCache(
}
} // namespace

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::ArchiveReplayService replayArchive(payloadPath,
opts.postbackDirectory);
// All the resource data must be in the archive file, no fallback resource
// loader to fetch uncached resources data.
auto onDiskCache = OnDiskResourceCache::create(opts.replayArchive, false);
std::unique_ptr<ResourceLoader> resLoader =
CachedResourceLoader::create(onDiskCache.get(), nullptr);

std::unique_ptr<Context> context = Context::create(
&replayArchive, crashHandler, resLoader.get(), &memoryManager);

if (context->initialize("payload")) {
GAPID_DEBUG("Replay context initialized successfully");
} else {
GAPID_ERROR("Replay context initialization failed");
return EXIT_FAILURE;
}

GAPID_INFO("Replay started");
bool ok = context->interpret();
replayArchive.sendReplayFinished();
if (!context->cleanup()) {
GAPID_ERROR("Replay cleanup failed");
return EXIT_FAILURE;
}
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(opts.logLevel, "gapir", opts.logPath);

static int startServer(core::CrashHandler* crashHandler, Options opts) {
// 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.
Expand Down Expand Up @@ -754,7 +806,7 @@ static int startServer(Options opts) {
PrewarmData data;
std::unique_ptr<Server> server =
Setup(uri.c_str(), (authToken.size() > 0) ? authToken.data() : nullptr,
cache.get(), opts.idleTimeoutSec, &crashHandler, &memoryManager,
cache.get(), opts.idleTimeoutSec, crashHandler, &memoryManager,
&data, &lock);
// The following message is parsed by launchers to detect the selected port.
// DO NOT CHANGE!
Expand Down Expand Up @@ -786,13 +838,25 @@ int main(int argc, const char* argv[]) {
} else if (opts.version) {
printf("GAPIR version " GAPID_VERSION_AND_BUILD "\n");
return EXIT_SUCCESS;
} else if (opts.mode == Options::kConflict) {
} else if (opts.mode == kConflict) {
GAPID_ERROR("Argument conflicts.");
return EXIT_FAILURE;
} else if (opts.mode == Options::kReplayArchive) {
return replayArchive(opts);
}

core::CrashHandler crashHandler;
GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath);

if (opts.mode == kReplayArchive) {
std::string payloadPath = std::string(opts.replayArchive) + "/payload.bin";
gapir::ArchiveReplayService replayArchiveService(payloadPath,
opts.postbackDirectory);
// All the resource data must be in the archive file, no fallback resource
// loader to fetch uncached resources data.
auto onDiskCache = OnDiskResourceCache::create(opts.replayArchive, false);
return replayArchive(&crashHandler, std::move(onDiskCache),
&replayArchiveService);
} else {
return startServer(opts);
return startServer(&crashHandler, opts);
}
}

Expand Down
1 change: 1 addition & 0 deletions cmd/gapit/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ go_library(
"//core/app/auth:go_default_library",
"//core/app/crash:go_default_library",
"//core/app/flags:go_default_library",
"//core/app/layout:go_default_library",
"//core/app/status:go_default_library",
"//core/data/endian:go_default_library",
"//core/data/id:go_default_library",
Expand Down
Loading

0 comments on commit fa041f6

Please sign in to comment.