1 /*
2  * Copyright (C) 2018 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 "tools/trace_to_text/trace_to_profile.h"
18 
19 #include <cxxabi.h>
20 #include <inttypes.h>
21 
22 #include <algorithm>
23 #include <map>
24 #include <set>
25 #include <vector>
26 
27 #include "tools/trace_to_text/utils.h"
28 
29 #include "perfetto/base/file_utils.h"
30 #include "perfetto/base/logging.h"
31 #include "perfetto/base/temp_file.h"
32 
33 #include "perfetto/trace/profiling/profile_packet.pb.h"
34 #include "perfetto/trace/trace.pb.h"
35 #include "perfetto/trace/trace_packet.pb.h"
36 
37 #include "third_party/pprof/profile.pb.h"
38 
39 namespace perfetto {
40 namespace trace_to_text {
41 
42 namespace {
43 
44 constexpr const char* kDefaultTmp = "/tmp";
45 
MaybeDemangle(std::string * name)46 void MaybeDemangle(std::string* name) {
47   int ignored;
48   char* data = abi::__cxa_demangle(name->c_str(), nullptr, nullptr, &ignored);
49   if (data) {
50     *name = data;
51     free(data);
52   }
53 }
54 
GetTemp()55 std::string GetTemp() {
56   const char* tmp = getenv("TMPDIR");
57   if (tmp == nullptr)
58     tmp = kDefaultTmp;
59   return tmp;
60 }
61 
62 using ::perfetto::protos::ProfilePacket;
63 
64 using GLine = ::perftools::profiles::Line;
65 using GMapping = ::perftools::profiles::Mapping;
66 using GLocation = ::perftools::profiles::Location;
67 using GProfile = ::perftools::profiles::Profile;
68 using GValueType = ::perftools::profiles::ValueType;
69 using GFunction = ::perftools::profiles::Function;
70 using GSample = ::perftools::profiles::Sample;
71 
ToHex(const std::string & build_id)72 std::string ToHex(const std::string& build_id) {
73   std::string hex_build_id(2 * build_id.size() + 1, ' ');
74   for (size_t i = 0; i < build_id.size(); ++i)
75     snprintf(&(hex_build_id[2 * i]), 3, "%02hhx", build_id[i]);
76   // Remove the trailing nullbyte.
77   hex_build_id.resize(2 * build_id.size());
78   return hex_build_id;
79 }
80 
81 enum Strings : int64_t {
82   kEmpty = 0,
83   kObjects,
84   kAllocObjects,
85   kCount,
86   kSpace,
87   kAllocSpace,
88   kBytes
89 };
90 
DumpProfilePacket(std::vector<ProfilePacket> & packet_fragments,const std::string & file_prefix)91 void DumpProfilePacket(std::vector<ProfilePacket>& packet_fragments,
92                        const std::string& file_prefix) {
93   std::map<uint64_t, std::string> string_lookup;
94   // A profile packet can be split into multiple fragments. We need to iterate
95   // over all of them to reconstruct the original packet.
96   for (const ProfilePacket& packet : packet_fragments) {
97     for (const ProfilePacket::InternedString& interned_string :
98          packet.strings())
99       string_lookup.emplace(interned_string.id(), interned_string.str());
100   }
101 
102   std::map<uint64_t, const std::vector<uint64_t>> callstack_lookup;
103   for (const ProfilePacket& packet : packet_fragments) {
104     for (const ProfilePacket::Callstack& callstack : packet.callstacks()) {
105       std::vector<uint64_t> frame_ids(
106           static_cast<size_t>(callstack.frame_ids().size()));
107       std::reverse_copy(callstack.frame_ids().cbegin(),
108                         callstack.frame_ids().cend(), frame_ids.begin());
109       callstack_lookup.emplace(callstack.id(), std::move(frame_ids));
110     }
111   }
112 
113   std::map<std::string, uint64_t> string_table;
114   string_table[""] = kEmpty;
115   string_table["objects"] = kObjects;
116   string_table["alloc_objects"] = kAllocObjects;
117   string_table["count"] = kCount;
118   string_table["space"] = kSpace;
119   string_table["alloc_space"] = kAllocSpace;
120   string_table["bytes"] = kBytes;
121 
122   GProfile profile;
123   GValueType* value_type = profile.add_sample_type();
124   value_type->set_type(kObjects);
125   value_type->set_unit(kCount);
126 
127   value_type = profile.add_sample_type();
128   value_type->set_type(kAllocObjects);
129   value_type->set_unit(kCount);
130 
131   value_type = profile.add_sample_type();
132   value_type->set_type(kAllocSpace);
133   value_type->set_unit(kBytes);
134 
135   // The last value is the default one selected.
136   value_type = profile.add_sample_type();
137   value_type->set_type(kSpace);
138   value_type->set_unit(kBytes);
139 
140   for (const ProfilePacket& packet : packet_fragments) {
141     for (const ProfilePacket::Mapping& mapping : packet.mappings()) {
142       GMapping* gmapping = profile.add_mapping();
143       gmapping->set_id(mapping.id());
144       gmapping->set_memory_start(mapping.start());
145       gmapping->set_memory_limit(mapping.end());
146       gmapping->set_file_offset(mapping.offset());
147       std::string filename;
148       for (uint64_t str_id : mapping.path_string_ids()) {
149         auto it = string_lookup.find(str_id);
150         if (it == string_lookup.end()) {
151           PERFETTO_ELOG("Mapping %" PRIu64
152                         " referring to invalid string_id %" PRIu64 ".",
153                         static_cast<uint64_t>(mapping.id()), str_id);
154           continue;
155         }
156 
157         filename += "/" + it->second;
158       }
159 
160       decltype(string_table)::iterator it;
161       std::tie(it, std::ignore) =
162           string_table.emplace(filename, string_table.size());
163       gmapping->set_filename(static_cast<int64_t>(it->second));
164 
165       auto str_it = string_lookup.find(mapping.build_id());
166       if (str_it != string_lookup.end()) {
167         const std::string& build_id = str_it->second;
168         std::tie(it, std::ignore) =
169             string_table.emplace(ToHex(build_id), string_table.size());
170         gmapping->set_build_id(static_cast<int64_t>(it->second));
171       }
172     }
173   }
174 
175   std::set<uint64_t> functions_to_dump;
176   for (const ProfilePacket& packet : packet_fragments) {
177     for (const ProfilePacket::Frame& frame : packet.frames()) {
178       GLocation* glocation = profile.add_location();
179       glocation->set_id(frame.id());
180       glocation->set_mapping_id(frame.mapping_id());
181       // TODO(fmayer): This is probably incorrect. Probably should be abs pc.
182       glocation->set_address(frame.rel_pc());
183       GLine* gline = glocation->add_line();
184       gline->set_function_id(frame.function_name_id());
185       functions_to_dump.emplace(frame.function_name_id());
186     }
187   }
188 
189   for (uint64_t function_name_id : functions_to_dump) {
190     auto str_it = string_lookup.find(function_name_id);
191     if (str_it == string_lookup.end()) {
192       PERFETTO_ELOG("Function referring to invalid string id %" PRIu64,
193                     function_name_id);
194       continue;
195     }
196     decltype(string_table)::iterator it;
197     std::string function_name = str_it->second;
198     // This assumes both the device that captured the trace and the host
199     // machine use the same mangling scheme. This is a reasonable
200     // assumption as the Itanium ABI is the de-facto standard for mangling.
201     MaybeDemangle(&function_name);
202     std::tie(it, std::ignore) =
203         string_table.emplace(std::move(function_name), string_table.size());
204     GFunction* gfunction = profile.add_function();
205     gfunction->set_id(function_name_id);
206     gfunction->set_name(static_cast<int64_t>(it->second));
207   }
208 
209   // We keep the interning table as string -> uint64_t for fast and easy
210   // lookup. When dumping, we need to turn it into a uint64_t -> string
211   // table so we get it sorted by key order.
212   std::map<uint64_t, std::string> inverted_string_table;
213   for (const auto& p : string_table)
214     inverted_string_table[p.second] = p.first;
215   for (const auto& p : inverted_string_table)
216     profile.add_string_table(p.second);
217 
218   std::map<uint64_t, std::vector<const ProfilePacket::ProcessHeapSamples*>>
219       heap_samples;
220   for (const ProfilePacket& packet : packet_fragments) {
221     for (const ProfilePacket::ProcessHeapSamples& samples :
222          packet.process_dumps()) {
223       heap_samples[samples.pid()].emplace_back(&samples);
224     }
225   }
226   for (const auto& p : heap_samples) {
227     GProfile cur_profile = profile;
228     uint64_t pid = p.first;
229     for (const ProfilePacket::ProcessHeapSamples* samples : p.second) {
230       if (samples->rejected_concurrent()) {
231         PERFETTO_ELOG("WARNING: The profile for %" PRIu64
232                       " was rejected due to a concurrent profile.",
233                       pid);
234       }
235       if (samples->buffer_overran()) {
236         PERFETTO_ELOG("WARNING: The profile for %" PRIu64
237                       " ended early due to a buffer overrun.",
238                       pid);
239       }
240       if (samples->buffer_corrupted()) {
241         PERFETTO_ELOG("WARNING: The profile for %" PRIu64
242                       " ended early due to a buffer corruption."
243                       " THIS IS ALWAYS A BUG IN HEAPPROFD OR"
244                       " CLIENT MEMORY CORRUPTION.",
245                       pid);
246       }
247 
248       for (const ProfilePacket::HeapSample& sample : samples->samples()) {
249         GSample* gsample = cur_profile.add_sample();
250         auto it = callstack_lookup.find(sample.callstack_id());
251         if (it == callstack_lookup.end()) {
252           PERFETTO_ELOG("Callstack referring to invalid callstack id %" PRIu64,
253                         static_cast<uint64_t>(sample.callstack_id()));
254           continue;
255         }
256         for (uint64_t frame_id : it->second)
257           gsample->add_location_id(frame_id);
258         gsample->add_value(
259             static_cast<int64_t>(sample.alloc_count() - sample.free_count()));
260         gsample->add_value(static_cast<int64_t>(sample.alloc_count()));
261         gsample->add_value(static_cast<int64_t>(sample.self_allocated()));
262         gsample->add_value(static_cast<int64_t>(sample.self_allocated() -
263                                                 sample.self_freed()));
264       }
265     }
266     std::string filename = file_prefix + std::to_string(pid) + ".pb";
267     base::ScopedFile fd(base::OpenFile(filename, O_CREAT | O_WRONLY, 0700));
268     if (!fd)
269       PERFETTO_FATAL("Failed to open %s", filename.c_str());
270     std::string serialized = cur_profile.SerializeAsString();
271     PERFETTO_CHECK(base::WriteAll(*fd, serialized.c_str(), serialized.size()) ==
272                    static_cast<ssize_t>(serialized.size()));
273   }
274 }
275 
276 }  // namespace
277 
TraceToProfile(std::istream * input,std::ostream * output)278 int TraceToProfile(std::istream* input, std::ostream* output) {
279   std::string temp_dir = GetTemp() + "/heap_profile-XXXXXXX";
280   size_t itr = 0;
281   PERFETTO_CHECK(mkdtemp(&temp_dir[0]));
282   std::vector<ProfilePacket> rolling_profile_packets;
283   ForEachPacketInTrace(input, [&temp_dir, &itr, &rolling_profile_packets](
284                                   const protos::TracePacket& packet) {
285     if (!packet.has_profile_packet())
286       return;
287     rolling_profile_packets.emplace_back(packet.profile_packet());
288     if (!packet.profile_packet().continued()) {
289       for (size_t i = 1; i < rolling_profile_packets.size(); ++i) {
290         // Ensure we are not missing a chunk.
291         PERFETTO_CHECK(rolling_profile_packets[i - 1].index() + 1 ==
292                        rolling_profile_packets[i].index());
293       }
294       DumpProfilePacket(rolling_profile_packets,
295                         temp_dir + "/heap_dump." + std::to_string(++itr) + ".");
296       rolling_profile_packets.clear();
297     }
298   });
299 
300   if (!rolling_profile_packets.empty()) {
301     *output << "WARNING: Truncated heap dump. Not generating profile."
302             << std::endl;
303   }
304 
305   *output << "Wrote profiles to " << temp_dir << std::endl;
306 
307   return 0;
308 }
309 
310 }  // namespace trace_to_text
311 }  // namespace perfetto
312