blob: b86680c9924d2089d7ad85bb15964af3679b991a [file] [edit]
/*
* Copyright (C) 2018 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 "src/traceconv/trace_to_profile.h"
#include <cerrno>
#include <cstdlib>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/profiling/pprof_builder.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/util/deobfuscation/deobfuscator.h"
#include "src/trace_processor/util/symbolizer/symbolize_database.h"
#include "src/traceconv/utils.h"
namespace {
constexpr const char* kDefaultTmp = "/tmp";
std::string GetTemp() {
if (auto tmp = getenv("TMPDIR"); tmp)
return tmp;
if (auto tmp = getenv("TEMP"); tmp)
return tmp;
return kDefaultTmp;
}
} // namespace
namespace perfetto {
namespace trace_to_text {
namespace {
uint64_t ToConversionFlags(bool annotate_frames) {
return static_cast<uint64_t>(annotate_frames
? ConversionFlags::kAnnotateFrames
: ConversionFlags::kNone);
}
void MaybeSymbolize(trace_processor::TraceProcessor* tp, bool verbose) {
profiling::SymbolizerConfig sym_config;
const char* mode = getenv("PERFETTO_SYMBOLIZER_MODE");
std::vector<std::string> paths = profiling::GetPerfettoBinaryPath();
if (paths.empty()) {
return;
}
if (mode && std::string_view(mode) == "find") {
sym_config.find_symbol_paths = std::move(paths);
} else {
sym_config.index_symbol_paths = std::move(paths);
}
auto result = profiling::SymbolizeDatabaseAndLog(tp, sym_config, verbose);
if (result.error == profiling::SymbolizerError::kOk &&
!result.symbols.empty()) {
IngestTraceOrDie(tp, result.symbols);
}
tp->Flush();
}
void MaybeDeobfuscate(trace_processor::TraceProcessor* tp) {
auto maybe_map = profiling::GetPerfettoProguardMapPath();
if (maybe_map.empty()) {
return;
}
profiling::ReadProguardMapsToDeobfuscationPackets(
maybe_map, [tp](const std::string& trace_proto) {
IngestTraceOrDie(tp, trace_proto);
});
tp->Flush();
}
std::string GetRandomString(size_t n) {
std::random_device r;
auto rng = std::default_random_engine(r());
std::string result(n, ' ');
for (size_t i = 0; i < n; ++i) {
result[i] = 'a' + (rng() % ('z' - 'a'));
}
return result;
}
// Creates the destination directory.
// If |output_dir| is not empty, it is used as the destination directory.
// Otherwise, a random temporary directory is created using
// |fallback_dirname_prefix|.
std::string GetDestinationDirectory(
const std::string& output_dir,
const std::string& fallback_dirname_prefix) {
std::string dst_dir;
if (!output_dir.empty()) {
dst_dir = output_dir;
} else {
dst_dir = GetTemp() + "/" + fallback_dirname_prefix +
base::GetTimeFmt("%y%m%d%H%M%S") + GetRandomString(5);
}
if (!base::Mkdir(dst_dir) && errno != EEXIST) {
PERFETTO_FATAL("Failed to create output directory %s", dst_dir.c_str());
}
return dst_dir;
}
std::optional<ConversionMode> DetectConversionMode(
trace_processor::TraceProcessor* tp) {
auto it = tp->ExecuteQuery(R"(
SELECT
EXISTS (SELECT 1 FROM __intrinsic_heap_profile_allocation LIMIT 1),
EXISTS (SELECT 1 FROM perf_sample LIMIT 1),
EXISTS (SELECT 1 FROM __intrinsic_heap_graph_object LIMIT 1)
)");
PERFETTO_CHECK(it.Next());
int64_t alloc_present = it.Get(0).AsLong();
int64_t perf_present = it.Get(1).AsLong();
int64_t graph_present = it.Get(2).AsLong();
int64_t count = alloc_present + perf_present + graph_present;
if (count == 0) {
PERFETTO_LOG("No profiles found.");
return std::nullopt;
} else if (count > 1) {
std::string err_msg =
"More than one type of profile found in the trace, pass an explicit "
"disambiguation flag:\n";
if (alloc_present)
err_msg += " --alloc: allocator profile\n";
if (perf_present)
err_msg += " --perf: perf profile\n";
if (graph_present)
err_msg += " --java-heap: java heap graph\n";
PERFETTO_ELOG("%s", err_msg.c_str());
return std::nullopt;
}
return alloc_present ? ConversionMode::kHeapProfile
: perf_present ? ConversionMode::kPerfProfile
: ConversionMode::kJavaHeapProfile;
}
} // namespace
int TraceToProfile(std::istream* input,
uint64_t pid,
const std::vector<uint64_t>& timestamps,
bool annotate_frames,
const std::string& output_dir,
std::optional<ConversionMode> explicit_mode,
bool verbose) {
// Pre-parse trace.
trace_processor::Config config;
std::unique_ptr<trace_processor::TraceProcessor> tp =
trace_processor::TraceProcessor::CreateInstance(config);
if (!ReadTraceUnfinalized(tp.get(), input))
return -1;
tp->Flush();
// Detect type of profile.
std::optional<ConversionMode> mode = explicit_mode.has_value()
? explicit_mode
: DetectConversionMode(tp.get());
if (!mode.has_value()) {
return -1;
}
int file_idx = 0;
std::function<std::string(const SerializedProfile&)> filename_fn;
std::string dir_prefix;
switch (*mode) {
case ConversionMode::kHeapProfile:
filename_fn = [&file_idx](const SerializedProfile& profile) {
return "heap_dump." + std::to_string(++file_idx) + "." +
std::to_string(profile.pid) + "." + profile.heap_name + ".pb";
};
dir_prefix = "heap_profile-";
break;
case ConversionMode::kPerfProfile:
filename_fn = [&file_idx](const SerializedProfile& profile) {
return "profile." + std::to_string(++file_idx) + ".pid." +
std::to_string(profile.pid) + ".pb";
};
dir_prefix = "perf_profile-";
break;
case ConversionMode::kJavaHeapProfile:
filename_fn = [&file_idx](const SerializedProfile& profile) {
return "java_heap_dump." + std::to_string(++file_idx) + "." +
std::to_string(profile.pid) + ".pb";
};
dir_prefix = "heap_profile-";
break;
}
// Add symbolisation and deobfuscation packets.
MaybeSymbolize(tp.get(), verbose);
MaybeDeobfuscate(tp.get());
if (auto status = tp->NotifyEndOfFile(); !status.ok()) {
return -1;
}
// Generate profiles.
std::vector<SerializedProfile> profiles;
TraceToPprof(tp.get(), &profiles, *mode, ToConversionFlags(annotate_frames),
pid, timestamps);
if (profiles.empty()) {
return 0;
}
// Write profiles to files.
std::string dst_dir = GetDestinationDirectory(output_dir, dir_prefix);
for (const auto& profile : profiles) {
std::string filename = dst_dir + "/" + filename_fn(profile);
base::ScopedFile fd(base::OpenFile(filename, O_CREAT | O_WRONLY, 0700));
if (!fd)
PERFETTO_FATAL("Failed to open %s", filename.c_str());
PERFETTO_CHECK(base::WriteAll(*fd, profile.serialized.c_str(),
profile.serialized.size()) ==
static_cast<ssize_t>(profile.serialized.size()));
}
PERFETTO_LOG("Wrote profiles to %s", dst_dir.c_str());
return 0;
}
} // namespace trace_to_text
} // namespace perfetto