diff options
| author | 2025-03-11 07:02:51 -0700 | |
|---|---|---|
| committer | 2025-03-11 07:02:51 -0700 | |
| commit | 97ee2b44cab0fa5d2fccecdab0be6f45ec9403c3 (patch) | |
| tree | 6fffb584a200fe85ced3f1dbd623b6433f294881 | |
| parent | e52e907c2a548d11f7a93ec92105ed6c21bf23b8 (diff) | |
| parent | 4b88637ae7a8fa459bcd3a22452770ec4858772f (diff) | |
Add a parser for processing long running method traces am: 4b88637ae7
Original change: https://android-review.googlesource.com/c/platform/art/+/3523158
Change-Id: I463b4af451c1070eafa8cda10e654b01eafbc4a6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
| -rw-r--r-- | tools/trace_parser/Android.bp | 41 | ||||
| -rw-r--r-- | tools/trace_parser/long_running_method_trace_parser.cc | 252 |
2 files changed, 293 insertions, 0 deletions
diff --git a/tools/trace_parser/Android.bp b/tools/trace_parser/Android.bp new file mode 100644 index 0000000000..f28af64c7f --- /dev/null +++ b/tools/trace_parser/Android.bp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2025 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "art_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["art_license"], +} + +art_cc_binary { + name: "long_running_method_trace_parser", + defaults: ["art_debug_defaults"], + host_supported: true, + device_supported: false, + srcs: [ + "long_running_method_trace_parser.cc", + ], + static_libs: [ + "libbase", + "libartbase", + "liblog", + "libz", + "libziparchive", + ], +} diff --git a/tools/trace_parser/long_running_method_trace_parser.cc b/tools/trace_parser/long_running_method_trace_parser.cc new file mode 100644 index 0000000000..9645a445f8 --- /dev/null +++ b/tools/trace_parser/long_running_method_trace_parser.cc @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2025 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 <map> +#include <memory> + +#include "base/leb128.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" + +namespace art { + +// These constants are defined in the ART sources in the following files: +// +// - art/runtime/trace.h +// - art/runtime/trace_profile.cc +static const int kThreadInfoHeaderV2 = 0; +static const int kMethodInfoHeaderV2 = 1; +static const int kEntryHeaderV2 = 2; +static const int kMethodEntry = 0; +static const int kMethodExit = 1; +static const int kAlwaysOnMethodInfoHeaderSize = 11; +static const int kAlwaysOnTraceHeaderSize = 12; + +uint64_t ReadNumber(int num_bytes, uint8_t* header) { + uint64_t number = 0; + for (int i = 0; i < num_bytes; i++) { + uint64_t c = header[i]; + number += c << (i * 8); + } + return number; +} + +bool ProcessMethodInfo(std::unique_ptr<File>& file, std::map<uint64_t, std::string>& name_map) { + // The first byte that specified the type of the packet is already read in + // ParseLongRunningMethodTrace. + uint8_t header[kAlwaysOnMethodInfoHeaderSize - 1]; + if (!file->ReadFully(&header, sizeof(header))) { + printf("Couldn't read header\n"); + return false; + } + uint64_t id = ReadNumber(8, header); + int length = ReadNumber(2, header + 8); + + std::unique_ptr<char[]> name(new char[length]); + if (!file->ReadFully(name.get(), length)) { + return false; + } + std::string str(name.get(), length); + std::replace(str.begin(), str.end(), '\t', ' '); + if (str[str.length() - 1] == '\n') { + str.erase(str.length() - 1); + } + name_map.emplace(id, str); + return true; +} + +void PrintTraceEntry(const std::string& method_name, + int event_type, + int* current_depth, + size_t timestamp) { + std::string entry; + for (int i = 0; i < *current_depth; i++) { + entry.push_back('.'); + } + if (event_type == kMethodEntry) { + *current_depth += 1; + entry.append(".>> "); + } else if (event_type == kMethodExit) { + *current_depth -= 1; + entry.append("<< "); + } else { + entry.append("?? "); + } + entry.append(" "); + entry.append(method_name); + entry.append(" "); + entry.append(std::to_string(timestamp)); + entry.append("\n"); + printf("%s", entry.c_str()); +} + +bool SkipTraceEntries(std::unique_ptr<File>& file) { + // The first byte that specified the type of the packet is already read in + // ParseLongRunningMethodTrace. + uint8_t header[kAlwaysOnTraceHeaderSize - 1]; + if (!file->ReadFully(header, sizeof(header))) { + return false; + } + + // Read thread id + ReadNumber(4, header); + int offset = 4; + // Read number of records + ReadNumber(3, header + offset); + offset += 3; + int total_size = ReadNumber(4, header + offset); + std::unique_ptr<uint8_t[]> buffer(new uint8_t[total_size]); + if (!file->ReadFully(buffer.get(), total_size)) { + return false; + } + return true; +} + +bool ProcessLongRunningMethodTraceEntries(std::unique_ptr<File>& file, + std::map<int64_t, int>& current_depth_map, + std::map<uint64_t, std::string>& method_map) { + // The first byte that specified the type of the packet is already read in + // ParseLongRunningMethodTrace. + uint8_t header[kAlwaysOnTraceHeaderSize - 1]; + if (!file->ReadFully(header, sizeof(header))) { + return false; + } + + uint32_t thread_id = ReadNumber(4, header); + int offset = 4; + int num_records = ReadNumber(3, header + offset); + offset += 3; + int total_size = ReadNumber(4, header + offset); + if (total_size == 0) { + return true; + } + std::unique_ptr<uint8_t[]> buffer(new uint8_t[total_size]); + if (!file->ReadFully(buffer.get(), total_size)) { + return false; + } + + printf("Thread: %d\n", thread_id); + int current_depth = 0; + if (current_depth_map.find(thread_id) != current_depth_map.end()) { + // Get the current call stack depth. If it is the first method we are seeing on this thread + // then this map wouldn't have an entry, and we start with the depth of 0. + current_depth = current_depth_map[thread_id]; + } + + const uint8_t* current_buffer_ptr = buffer.get(); + const uint8_t* end_ptr = buffer.get() + total_size; + uint64_t prev_method_id = 0; + int64_t prev_timestamp_and_action = 0; + for (int i = 0; i < num_records; i++) { + // Read timestamp and action + int64_t ts_diff = 0; + if (!DecodeSignedLeb128Checked(¤t_buffer_ptr, end_ptr, &ts_diff)) { + LOG(FATAL) << "Reading past the buffer when decoding timestamp"; + } + int64_t timestamp_and_action = prev_timestamp_and_action + ts_diff; + prev_timestamp_and_action = timestamp_and_action; + bool is_method_exit = timestamp_and_action & 0x1; + + uint64_t method_id; + std::string method_name; + if (!is_method_exit) { + int64_t method_diff = 0; + if (!DecodeSignedLeb128Checked(¤t_buffer_ptr, end_ptr, &method_diff)) { + LOG(FATAL) << "Reading past the buffer when decoding method id"; + } + method_id = prev_method_id + method_diff; + prev_method_id = method_id; + if (method_map.find(method_id) == method_map.end()) { + LOG(FATAL) << "No entry for method " << std::hex << method_id; + } + method_name = method_map[method_id]; + } + + PrintTraceEntry(method_name, + is_method_exit? kMethodExit: kMethodEntry, + ¤t_depth, + timestamp_and_action & ~0x1); + } + current_depth_map[thread_id] = current_depth; + return true; +} + +void ParseLongRunningMethodTrace(char* file_name) { + std::unique_ptr<File> file(OS::OpenFileForReading(file_name)); + if (file == nullptr) { + printf("Couldn't open file\n"); + return; + } + + // Map to maintain information about threads and methods + std::map<uint64_t, std::string> method_map; + + // Map to Maintain the current depth of the method in the call stack. Used to + // correctly indent when printing the trace events. + std::map<int64_t, int> current_depth_map; + + // First parse metadata. To keep the implementation of dumping the data + // simple, we don't ensure that the information about methods is dumped before the + // methods. This is also good if the ANR report got truncated. We will then + // have information about how long the methods took and we can infer some of + // the method names from the stack trace. + while (true) { + uint8_t entry_header; + if (!file->ReadFully(&entry_header, sizeof(entry_header))) { + break; + } + if (entry_header == kEntryHeaderV2) { + if (!SkipTraceEntries(file)) { + break; + } + } else { + DCHECK_EQ(entry_header, kMethodInfoHeaderV2); + if (!ProcessMethodInfo(file, method_map)) { + break; + } + } + } + + // Reset file + file->ResetOffset(); + + while (true) { + uint8_t entry_header; + if (!file->ReadFully(&entry_header, sizeof(entry_header))) { + break; + } + if (entry_header != kEntryHeaderV2) { + break; + } + if (!ProcessLongRunningMethodTraceEntries(file, current_depth_map, method_map)) { + break; + } + } +} + +} // namespace art + +int main(int argc, char **argv) { + if (argc < 1) { + printf("Usage trace <filename>"); + return -1; + } + + art::ParseLongRunningMethodTrace(argv[1]); + return 0; +} |