/* * 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 "perfetto/ext/traceconv/traceconv.h" #include #include #include #include #include #include #include #include #include #include #include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/base/version.h" #include "perfetto/profiling/pprof_builder.h" #include "src/protozero/text_to_proto/text_to_proto.h" #include "src/traceconv/android_extension.descriptor.h" #include "src/traceconv/deobfuscate_profile.h" #include "src/traceconv/symbolize_profile.h" #include "src/traceconv/trace.descriptor.h" #include "src/traceconv/trace_to_bundle.h" #include "src/traceconv/trace_to_firefox.h" #include "src/traceconv/trace_to_json.h" #include "src/traceconv/trace_to_profile.h" #include "src/traceconv/trace_to_systrace.h" #include "src/traceconv/trace_to_text.h" #include "src/traceconv/trace_unpack.h" #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) #include #include #else #include #endif namespace perfetto::traceconv { namespace { int Usage(const char* argv0) { fprintf(stderr, R"( Trace format conversion tool. Usage: %s MODE [OPTIONS] [input_file] [output_file] CONVERSION MODES AND THEIR SUPPORTED OPTIONS: systrace Converts to systrace HTML format --truncate [start|end] Truncates trace to keep start or end --full-sort Forces full trace sorting json Converts to Chrome JSON format --truncate [start|end] Truncates trace to keep start or end --full-sort Forces full trace sorting ctrace Converts to compressed systrace format --truncate [start|end] Truncates trace to keep start or end --full-sort Forces full trace sorting text Converts to human-readable text format --skip-unknown Skip unknown fields when converting to text profile Converts profile data to pprof format (default: auto-detect profile type) --alloc Convert only the allocator profile --perf Convert only the perf profile --java-heap Convert only the heap graph profile --no-annotations Don't add derived annotations to frames --timestamps T1,T2,... Generate profiles for specific timestamps --pid PID Generate profiles for specific process --output-dir DIR Output directory for profiles (default: random tmp) java_heap_profile Legacy alias for "profile --java-heap" symbolize Symbolizes addresses in profiles (no additional options) deobfuscate Deobfuscates obfuscated profiles (no additional options) firefox Converts to Firefox profiler format (no additional options) decompress_packets Decompresses compressed trace packets (no additional options) bundle Creates bundle with trace + debug data (outputs TAR with symbols/deobfuscation mappings) Requires input and output file paths (no stdin/stdout) --symbol-paths PATH1,PATH2,... Additional paths to search for symbols (beyond automatic discovery) --no-auto-symbol-paths Disable automatic symbol path discovery --proguard-map [pkg=]PATH ProGuard/R8 mapping.txt for Java/Kotlin deobfuscation (may be repeated). pkg= prefix scopes the map to a package. --no-auto-proguard-maps Disable automatic ProGuard/R8 mapping discovery (e.g. Gradle project layout) --verbose Print more detailed output binary Converts text proto to binary format (no additional options) NOTES: - If no input file is specified, reads from stdin - If no output file is specified, writes to stdout - Input/output files can be '-' to explicitly use stdin/stdout )", argv0); return 1; } uint64_t StringToUint64OrDie(const char* str) { char* end; uint64_t number = static_cast(strtoll(str, &end, 10)); if (*end != '\0') { PERFETTO_ELOG("Invalid %s. Expected decimal integer.", str); exit(1); } return number; } int TextToTrace(std::istream* input, std::ostream* output) { std::string trace_text(std::istreambuf_iterator{*input}, std::istreambuf_iterator{}); std::vector descriptors; descriptors.reserve(kTraceDescriptor.size() + kAndroidExtensionDescriptor.size()); descriptors.insert(descriptors.end(), kTraceDescriptor.begin(), kTraceDescriptor.end()); descriptors.insert(descriptors.end(), kAndroidExtensionDescriptor.begin(), kAndroidExtensionDescriptor.end()); auto proto_status = protozero::TextToProto(descriptors.data(), descriptors.size(), ".perfetto.protos.Trace", "trace", trace_text); if (!proto_status.ok()) { PERFETTO_ELOG("Failed to parse trace: %s", proto_status.status().c_message()); return 1; } const std::vector& trace_proto = proto_status.value(); output->write(reinterpret_cast(trace_proto.data()), static_cast(trace_proto.size())); return 0; } int Main(int argc, char** argv) { std::vector positional_args; trace_to_text::Keep truncate_keep = trace_to_text::Keep::kAll; uint64_t pid = 0; std::vector timestamps; bool full_sort = false; std::optional profile_type; bool profile_no_annotations = false; std::vector symbol_paths; std::vector proguard_maps; bool no_auto_symbol_paths = false; bool no_auto_proguard_maps = false; bool verbose = false; bool skip_unknown_fields = false; std::string output_dir; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) { printf("%s\n", base::GetVersionString()); return 0; } if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--truncate") == 0) { i++; if (i <= argc && strcmp(argv[i], "start") == 0) { truncate_keep = trace_to_text::Keep::kStart; } else if (i <= argc && strcmp(argv[i], "end") == 0) { truncate_keep = trace_to_text::Keep::kEnd; } else { PERFETTO_ELOG( "--truncate must specify whether to keep the end or the " "start of the trace."); return Usage(argv[0]); } } else if (i <= argc && strcmp(argv[i], "--pid") == 0) { i++; pid = StringToUint64OrDie(argv[i]); } else if (i <= argc && strcmp(argv[i], "--timestamps") == 0) { i++; std::vector ts_strings = base::SplitString(argv[i], ","); for (const std::string& ts : ts_strings) { timestamps.emplace_back(StringToUint64OrDie(ts.c_str())); } } else if (strcmp(argv[i], "--alloc") == 0) { profile_type = trace_to_text::ConversionMode::kHeapProfile; } else if (strcmp(argv[i], "--perf") == 0) { profile_type = trace_to_text::ConversionMode::kPerfProfile; } else if (strcmp(argv[i], "--java-heap") == 0) { profile_type = trace_to_text::ConversionMode::kJavaHeapProfile; } else if (strcmp(argv[i], "--no-annotations") == 0) { profile_no_annotations = true; } else if (strcmp(argv[i], "--full-sort") == 0) { full_sort = true; } else if (i < argc && strcmp(argv[i], "--symbol-paths") == 0) { i++; symbol_paths = base::SplitString(argv[i], ","); } else if (strcmp(argv[i], "--no-auto-symbol-paths") == 0) { no_auto_symbol_paths = true; } else if (strcmp(argv[i], "--no-auto-proguard-maps") == 0) { no_auto_proguard_maps = true; } else if (i < argc && strcmp(argv[i], "--proguard-map") == 0) { i++; if (i >= argc) { PERFETTO_ELOG("--proguard-map requires an argument."); return Usage(argv[0]); } std::string arg = argv[i]; size_t eq = arg.find('='); trace_to_text::ProguardMapSpec spec; if (eq == std::string::npos) { spec.path = std::move(arg); } else { spec.package = arg.substr(0, eq); spec.path = arg.substr(eq + 1); } proguard_maps.push_back(std::move(spec)); } else if (strcmp(argv[i], "--verbose") == 0) { verbose = true; } else if (i < argc && strcmp(argv[i], "--output-dir") == 0) { i++; if (i >= argc) { PERFETTO_ELOG("--output-dir requires an argument."); return Usage(argv[0]); } output_dir = argv[i]; } else if (strcmp(argv[i], "--skip-unknown") == 0) { skip_unknown_fields = true; } else { positional_args.push_back(argv[i]); } } if (positional_args.empty()) return Usage(argv[0]); std::istream* input_stream; std::ifstream file_istream; if (positional_args.size() > 1) { const char* file_path = positional_args[1]; file_istream.open(file_path, std::ios_base::in | std::ios_base::binary); if (!file_istream.is_open()) PERFETTO_FATAL("Could not open %s", file_path); input_stream = &file_istream; } else { #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) if (isatty(STDIN_FILENO)) { PERFETTO_ELOG("Reading from stdin but it's connected to a TTY"); PERFETTO_LOG("It is unlikely that you want to type in some binary."); PERFETTO_LOG("Either pass a file path to the cmdline or pipe stdin"); return Usage(argv[0]); } #endif input_stream = &std::cin; } #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) // We don't want the runtime to replace "\n" with "\r\n" on `std::cout`. _setmode(_fileno(stdout), _O_BINARY); #endif std::ostream* output_stream; std::ofstream file_ostream; if (positional_args.size() > 2) { const char* file_path = positional_args[2]; file_ostream.open(file_path, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary); if (!file_ostream.is_open()) PERFETTO_FATAL("Could not open %s", file_path); output_stream = &file_ostream; } else { output_stream = &std::cout; } std::string format(positional_args[0]); if ((format != "profile" && format != "java_heap_profile") && (pid != 0 || !timestamps.empty())) { PERFETTO_ELOG( "--pid and --timestamps are supported only for profile " "and java_heap_profile formats."); return 1; } if ((format != "profile" && format != "java_heap_profile") && !output_dir.empty()) { PERFETTO_ELOG( "--output-dir is supported only for profile and java_heap_profile " "formats."); return 1; } if (format == "binary") { return TextToTrace(input_stream, output_stream); } if (format == "json") return trace_to_text::TraceToJson(input_stream, output_stream, /*compress=*/false, truncate_keep, full_sort); if (format == "systrace") return trace_to_text::TraceToSystrace(input_stream, output_stream, /*ctrace=*/false, truncate_keep, full_sort); if (format == "ctrace") return trace_to_text::TraceToSystrace(input_stream, output_stream, /*ctrace=*/true, truncate_keep, full_sort); if (truncate_keep != trace_to_text::Keep::kAll) { PERFETTO_ELOG( "--truncate is unsupported for " "text|profile|symbolize|decompress_packets format."); return 1; } if (full_sort) { PERFETTO_ELOG( "--full-sort is unsupported for " "text|profile|symbolize|decompress_packets format."); return 1; } if (format == "text") { trace_to_text::TraceToTextOptions options; options.skip_unknown_fields = skip_unknown_fields; return trace_to_text::TraceToText(input_stream, output_stream, options) ? 0 : 1; } if (format == "profile") { if (positional_args.size() > 2) { PERFETTO_ELOG( "output file is not supported for \"profile\", use --output-dir " "instead"); return Usage(argv[0]); } return trace_to_text::TraceToProfile(input_stream, pid, timestamps, !profile_no_annotations, output_dir, profile_type, verbose); } if (format == "java_heap_profile") { // legacy alias for "profile --java-heap" return trace_to_text::TraceToProfile( input_stream, pid, timestamps, !profile_no_annotations, output_dir, trace_to_text::ConversionMode::kJavaHeapProfile, verbose); } if (format == "symbolize") return trace_to_text::SymbolizeProfile(input_stream, output_stream, verbose); if (format == "deobfuscate") return trace_to_text::DeobfuscateProfile(input_stream, output_stream); if (format == "firefox") return trace_to_text::TraceToFirefoxProfile(input_stream, output_stream); if (format == "decompress_packets") return trace_to_text::UnpackCompressedPackets(input_stream, output_stream); if (format == "bundle") { // Bundle mode requires both input and output file paths if (positional_args.size() < 3) { PERFETTO_ELOG("Bundle mode requires both input and output file paths"); return Usage(argv[0]); } const char* input_file = positional_args[1]; const char* output_file = positional_args[2]; // Validate that stdin/stdout are not used for bundle mode if (strcmp(input_file, "-") == 0) { PERFETTO_ELOG( "Bundle mode does not support stdin input, provide file path"); return 1; } if (strcmp(output_file, "-") == 0) { PERFETTO_ELOG( "Bundle mode does not support stdout output, provide file path"); return 1; } // Validate input file exists and is readable if (!base::FileExists(input_file)) { PERFETTO_ELOG("Input file does not exist: %s", input_file); return 1; } trace_to_text::BundleContext context; context.symbol_paths = symbol_paths; context.proguard_maps = std::move(proguard_maps); context.no_auto_symbol_paths = no_auto_symbol_paths; context.no_auto_proguard_maps = no_auto_proguard_maps; context.verbose = verbose; if (const char* val = getenv("ANDROID_PRODUCT_OUT")) { context.android_product_out = val; } if (const char* val = getenv("HOME")) { context.home_dir = val; } context.root_dir = "/"; return trace_to_text::TraceToBundle(input_file, output_file, context); } return Usage(argv[0]); } } // namespace int TraceconvMain(int argc, char** argv) { return Main(argc, argv); } } // namespace perfetto::traceconv