blob: 7f933736e556fa724db7340880adeeb35c5b60c8 [file] [log] [blame]
/*
* Copyright (C) 2019 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.
*/
#include <stdio.h>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ziparchive/zip_writer.h>
#include "command.h"
#include "event_type.h"
#include "environment.h"
#include "utils.h"
#include "workload.h"
namespace {
const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
class PrepareCommand : public Command {
public:
PrepareCommand()
: Command("api-prepare", "Prepare recording via app api",
"Usage: simpleperf api-prepare\n"
) {}
bool Run(const std::vector<std::string>& args);
};
bool PrepareCommand::Run(const std::vector<std::string>&) {
// Enable profiling.
if (!CheckPerfEventLimit()) {
return false;
}
// Create tracepoint_events file.
if (!android::base::WriteStringToFile(GetTracepointEvents(),
"/data/local/tmp/tracepoint_events")) {
PLOG(ERROR) << "failed to write tracepoint_events file";
return false;
}
return true;
}
class CollectCommand : public Command {
public:
CollectCommand()
: Command("api-collect", "Collect recording data generated by app api",
// clang-format off
"Usage: simpleperf api-collect [options]\n"
"--app <package_name> the android application having recording data\n"
"-o record_zipfile_path the path to store recording data\n"
" Default is simpleperf_data.zip.\n"
#if 0
// Below options are only used internally and shouldn't be visible to the public.
"--in-app We are already running in the app's context.\n"
"--out-fd <fd> Write output to a file descriptor.\n"
"--stop-signal-fd <fd> Stop recording when fd is readable.\n"
#endif
// clang-format on
) {}
bool Run(const std::vector<std::string>& args);
private:
bool ParseOptions(const std::vector<std::string>& args);
void HandleStopSignal();
bool CollectRecordingData();
bool RemoveRecordingData();
std::string app_name_;
std::string output_filepath_ = "simpleperf_data.zip";
bool in_app_context_ = false;
android::base::unique_fd out_fd_;
android::base::unique_fd stop_signal_fd_;
};
bool CollectCommand::Run(const std::vector<std::string>& args) {
if (!ParseOptions(args)) {
return false;
}
if (in_app_context_) {
HandleStopSignal();
return CollectRecordingData() && RemoveRecordingData();
}
return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false);
}
bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
for (size_t i = 0; i < args.size(); ++i) {
if (args[i] == "--app") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
app_name_ = args[i];
} else if (args[i] == "--in-app") {
in_app_context_ = true;
} else if (args[i] == "-o") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
output_filepath_ = args[i];
} else if (args[i] == "--out-fd") {
int fd;
if (!GetUintOption(args, &i, &fd)) {
return false;
}
out_fd_.reset(fd);
} else if (args[i] == "--stop-signal-fd") {
int fd;
if (!GetUintOption(args, &i, &fd)) {
return false;
}
stop_signal_fd_.reset(fd);
} else {
ReportUnknownOption(args, i);
return false;
}
}
if (!in_app_context_) {
if (app_name_.empty()) {
LOG(ERROR) << "--app is missing";
return false;
}
}
return true;
}
void CollectCommand::HandleStopSignal() {
int fd = stop_signal_fd_.release();
std::thread thread([fd]() {
char c;
static_cast<void>(read(fd, &c, 1));
exit(1);
});
thread.detach();
}
bool CollectCommand::CollectRecordingData() {
std::unique_ptr<FILE, decltype(&fclose)> fp(android::base::Fdopen(std::move(out_fd_), "w"),
fclose);
if (fp == nullptr) {
PLOG(ERROR) << "failed to call fdopen";
return false;
}
std::vector<char> buffer(64 * 1024);
ZipWriter zip_writer(fp.get());
for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) {
// No need to collect temporary files.
const std::string path = SIMPLEPERF_DATA_DIR + "/" + name;
if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) {
continue;
}
int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress);
if (result != 0) {
LOG(ERROR) << "failed to start zip entry " << name << ": "
<< zip_writer.ErrorCodeString(result);
return false;
}
android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path));
if (in_fd == -1) {
PLOG(ERROR) << "failed to open " << path;
return false;
}
while (true) {
ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size()));
if (nread < 0) {
PLOG(ERROR) << "failed to read " << path;
return false;
}
if (nread == 0) {
break;
}
result = zip_writer.WriteBytes(buffer.data(), nread);
if (result != 0) {
LOG(ERROR) << "failed to write zip entry " << name << ": "
<< zip_writer.ErrorCodeString(result);
return false;
}
}
result = zip_writer.FinishEntry();
if (result != 0) {
LOG(ERROR) << "failed to finish zip entry " << name << ": "
<< zip_writer.ErrorCodeString(result);
return false;
}
}
int result = zip_writer.Finish();
if (result != 0) {
LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result);
return false;
}
return true;
}
bool CollectCommand::RemoveRecordingData() {
return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR});
}
} // namespace
void RegisterAPICommands() {
RegisterCommand("api-prepare",
[]{ return std::unique_ptr<Command>(new PrepareCommand()); });
RegisterCommand("api-collect",
[]{ return std::unique_ptr<Command>(new CollectCommand()); });
}