xref: /aosp_15_r20/art/test/2246-trace-v2/dump_trace.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 
19 #include <map>
20 #include <memory>
21 
22 #include "base/leb128.h"
23 #include "base/os.h"
24 #include "base/unix_file/fd_file.h"
25 #include "jni.h"
26 
27 namespace art {
28 namespace {
29 
30 static const int kMagicValue = 0x574f4c53;
31 static const int kVersionDualClockStreaming = 0xf5;
32 static const int kVersionDualClock = 0x05;
33 static const int kThreadInfo = 0;
34 static const int kMethodInfo = 1;
35 static const int kTraceEntries = 2;
36 static const int kTraceActionBits = 2;
37 static const int kSummary = 3;
38 static const int kMethodEntry = 0;
39 static const int kMethodExitNormal = 1;
40 static const int kMethodExitError = 2;
41 
42 // List of methods that could be triggered by a GC. It isn't possible to control
43 // when GCs happen especially in gcstress configs. So we ignore certain methods
44 // that could be executed based on when GC occurs.
45 static const std::string_view ignored_methods_list[] = {
46     "java.lang.ref.ReferenceQueue add (Ljava/lang/ref/Reference;)V ReferenceQueue.java"
47 };
48 
ReadNumber(int num_bytes,uint8_t * header)49 uint64_t ReadNumber(int num_bytes, uint8_t* header) {
50   uint64_t number = 0;
51   for (int i = 0; i < num_bytes; i++) {
52     uint64_t c = header[i];
53     number += c << (i * 8);
54   }
55   return number;
56 }
57 
ProcessThreadOrMethodInfo(std::unique_ptr<File> & file,std::map<uint64_t,std::string> & name_map,bool is_method)58 bool ProcessThreadOrMethodInfo(std::unique_ptr<File>& file,
59                                std::map<uint64_t, std::string>& name_map,
60                                bool is_method) {
61   uint8_t header[10];
62   uint8_t header_length = is_method ? 10 : 6;
63   if (!file->ReadFully(&header, header_length)) {
64     printf("Couldn't read header\n");
65     return false;
66   }
67   uint8_t num_bytes_for_id = is_method ? 8 : 4;
68   uint64_t id = ReadNumber(num_bytes_for_id, header);
69   int length = ReadNumber(2, header + num_bytes_for_id);
70 
71   char* name = new char[length];
72   if (!file->ReadFully(name, length)) {
73     delete[] name;
74     return false;
75   }
76   std::string str(name, length);
77   std::replace(str.begin(), str.end(), '\t', ' ');
78   if (str[str.length() - 1] == '\n') {
79     str.erase(str.length() - 1);
80   }
81   name_map.emplace(id, str);
82   delete[] name;
83   return true;
84 }
85 
MethodInIgnoreList(const std::string & method_name)86 bool MethodInIgnoreList(const std::string& method_name) {
87   for (const std::string_view& ignored_method : ignored_methods_list) {
88     if (method_name.compare(ignored_method) == 0) {
89       return true;
90     }
91   }
92   return false;
93 }
94 
PrintTraceEntry(const std::string & thread_name,const std::string & method_name,int event_type,int * current_depth,std::string & ignored_method,int * ignored_method_depth)95 void PrintTraceEntry(const std::string& thread_name,
96                      const std::string& method_name,
97                      int event_type,
98                      int* current_depth,
99                      std::string& ignored_method,
100                      int* ignored_method_depth) {
101   bool ignore_trace_entry = false;
102   if (ignored_method.empty()) {
103     // Check if we need to ignore the method.
104     if (MethodInIgnoreList(method_name)) {
105       CHECK_EQ(event_type, kMethodEntry);
106       ignored_method = method_name;
107       *ignored_method_depth = *current_depth;
108       ignore_trace_entry = true;
109     }
110   } else {
111     // Check if the ignored method is exiting.
112     if (MethodInIgnoreList(method_name) && *current_depth == *ignored_method_depth + 1) {
113       CHECK_NE(event_type, kMethodEntry);
114       ignored_method.clear();
115     }
116     ignore_trace_entry = true;
117   }
118   std::string entry;
119   for (int i = 0; i < *current_depth; i++) {
120     entry.push_back('.');
121   }
122   if (event_type == kMethodEntry) {
123     *current_depth += 1;
124     entry.append(".>> ");
125   } else if (event_type == kMethodExitNormal) {
126     *current_depth -= 1;
127     entry.append("<< ");
128   } else if (event_type == kMethodExitError) {
129     *current_depth -= 1;
130     entry.append("<<E ");
131   } else {
132     entry.append("?? ");
133   }
134   entry.append(thread_name);
135   entry.append(" ");
136   entry.append(method_name);
137   entry.append("\n");
138   if (!ignore_trace_entry) {
139     printf("%s", entry.c_str());
140   }
141 }
142 
ProcessTraceEntries(std::unique_ptr<File> & file,std::map<int64_t,int> & current_depth_map,std::map<uint64_t,std::string> & thread_map,std::map<uint64_t,std::string> & method_map,bool is_dual_clock,const char * thread_name_filter,std::map<uint64_t,std::string> & ignored_method_map,std::map<int64_t,int> & ignored_method_depth_map)143 bool ProcessTraceEntries(std::unique_ptr<File>& file,
144                          std::map<int64_t, int>& current_depth_map,
145                          std::map<uint64_t, std::string>& thread_map,
146                          std::map<uint64_t, std::string>& method_map,
147                          bool is_dual_clock,
148                          const char* thread_name_filter,
149                          std::map<uint64_t, std::string>& ignored_method_map,
150                          std::map<int64_t, int>& ignored_method_depth_map) {
151   uint8_t header[11];
152   if (!file->ReadFully(header, sizeof(header))) {
153     return false;
154   }
155 
156   uint32_t thread_id = ReadNumber(4, header);
157   int offset = 4;
158   int num_records = ReadNumber(3, header + offset);
159   offset += 3;
160   int total_size = ReadNumber(4, header + offset);
161   uint8_t* buffer = new uint8_t[total_size];
162   if (!file->ReadFully(buffer, total_size)) {
163     delete[] buffer;
164     return false;
165   }
166 
167   int current_depth = 0;
168   if (current_depth_map.find(thread_id) != current_depth_map.end()) {
169     // Get the current call stack depth. If it is the first method we are seeing on this thread
170     // then this map wouldn't haven an entry we start with the depth of 0.
171     current_depth = current_depth_map[thread_id];
172   }
173 
174   int ignored_method_depth = 0;
175   std::string ignored_method;
176   if (ignored_method_map.find(thread_id) != ignored_method_map.end()) {
177     ignored_method = ignored_method_map[thread_id];
178     ignored_method_depth = ignored_method_depth_map[thread_id];
179   }
180 
181   std::string thread_name = thread_map[thread_id];
182   bool print_thread_events = (thread_name.compare(thread_name_filter) == 0);
183 
184   const uint8_t* current_buffer_ptr = buffer;
185   int64_t prev_method_value = 0;
186   for (int i = 0; i < num_records; i++) {
187     int64_t diff = 0;
188     if (!DecodeSignedLeb128Checked(&current_buffer_ptr, buffer + total_size - 1, &diff)) {
189       LOG(FATAL) << "Reading past the buffer???";
190     }
191     int64_t curr_method_value = prev_method_value + diff;
192     prev_method_value = curr_method_value;
193     uint8_t event_type = curr_method_value & 0x3;
194     uint64_t method_id = (curr_method_value >> kTraceActionBits) << kTraceActionBits;
195     if (method_map.find(method_id) == method_map.end()) {
196       LOG(FATAL) << "No entry for method " << std::hex << method_id;
197     }
198     if (print_thread_events) {
199       PrintTraceEntry(thread_name,
200                       method_map[method_id],
201                       event_type,
202                       &current_depth,
203                       ignored_method,
204                       &ignored_method_depth);
205     }
206     // Read timestamps
207     DecodeUnsignedLeb128(&current_buffer_ptr);
208     if (is_dual_clock) {
209       DecodeUnsignedLeb128(&current_buffer_ptr);
210     }
211   }
212   current_depth_map[thread_id] = current_depth;
213   if (!ignored_method.empty()) {
214     ignored_method_map[thread_id] = ignored_method;
215     ignored_method_depth_map[thread_id] = ignored_method_depth;
216   }
217   return true;
218 }
219 
Java_Main_dumpTrace(JNIEnv * env,jclass,jstring fileName,jstring threadName)220 extern "C" JNIEXPORT void JNICALL Java_Main_dumpTrace(JNIEnv* env,
221                                                       jclass,
222                                                       jstring fileName,
223                                                       jstring threadName) {
224   const char* file_name = env->GetStringUTFChars(fileName, nullptr);
225   const char* thread_name = env->GetStringUTFChars(threadName, nullptr);
226   std::map<uint64_t, std::string> thread_map;
227   std::map<uint64_t, std::string> method_map;
228   std::map<uint64_t, std::string> ignored_method_map;
229   std::map<int64_t, int> current_depth_map;
230   std::map<int64_t, int> ignored_method_depth_map;
231 
232   std::unique_ptr<File> file(OS::OpenFileForReading(file_name));
233   if (file == nullptr) {
234     printf("Couldn't open file\n");
235     return;
236   }
237 
238   uint8_t header[32];
239   if (!file->ReadFully(&header, sizeof(header))) {
240     printf("Couldn't read header\n");
241     return;
242   }
243   int magic_value = ReadNumber(4, header);
244   if (magic_value != kMagicValue) {
245     printf("Incorrect magic value got:%0x expected:%0x\n", magic_value, kMagicValue);
246     return;
247   }
248   int version = ReadNumber(2, header + 4);
249   printf("version=%0x\n", version);
250 
251   bool is_dual_clock = (version == kVersionDualClock) || (version == kVersionDualClockStreaming);
252   bool has_entries = true;
253   while (has_entries) {
254     uint8_t entry_header;
255     if (!file->ReadFully(&entry_header, sizeof(entry_header))) {
256       break;
257     }
258     switch (entry_header) {
259       case kThreadInfo:
260         if (!ProcessThreadOrMethodInfo(file, thread_map, /*is_method=*/false)) {
261           has_entries = false;
262         }
263         break;
264       case kMethodInfo:
265         if (!ProcessThreadOrMethodInfo(file, method_map, /*is_method=*/true)) {
266           has_entries = false;
267         }
268         break;
269       case kTraceEntries:
270         ProcessTraceEntries(file,
271                             current_depth_map,
272                             thread_map,
273                             method_map,
274                             is_dual_clock,
275                             thread_name,
276                             ignored_method_map,
277                             ignored_method_depth_map);
278         break;
279       case kSummary:
280         has_entries = false;
281         break;
282       default:
283         printf("Invalid Header %d\n", entry_header);
284         has_entries = false;
285         break;
286     }
287   }
288 
289   env->ReleaseStringUTFChars(fileName, file_name);
290   env->ReleaseStringUTFChars(threadName, thread_name);
291 }
292 
293 }  // namespace
294 }  // namespace art
295