xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/proto/v8_tracker.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 "src/trace_processor/importers/proto/v8_tracker.h"
18 
19 #include <cstdint>
20 #include <memory>
21 #include <optional>
22 #include <string>
23 #include <utility>
24 
25 #include "perfetto/base/logging.h"
26 #include "perfetto/base/status.h"
27 #include "perfetto/ext/base/base64.h"
28 #include "perfetto/ext/base/flat_hash_map.h"
29 #include "perfetto/ext/base/string_view.h"
30 #include "perfetto/protozero/field.h"
31 #include "perfetto/trace_processor/trace_blob.h"
32 #include "perfetto/trace_processor/trace_blob_view.h"
33 #include "protos/perfetto/trace/chrome/v8.pbzero.h"
34 #include "src/trace_processor/importers/common/address_range.h"
35 #include "src/trace_processor/importers/common/jit_cache.h"
36 #include "src/trace_processor/importers/common/mapping_tracker.h"
37 #include "src/trace_processor/importers/common/process_tracker.h"
38 #include "src/trace_processor/importers/proto/jit_tracker.h"
39 #include "src/trace_processor/importers/proto/string_encoding_utils.h"
40 #include "src/trace_processor/storage/stats.h"
41 #include "src/trace_processor/storage/trace_storage.h"
42 #include "src/trace_processor/tables/jit_tables_py.h"
43 #include "src/trace_processor/tables/v8_tables_py.h"
44 #include "src/trace_processor/types/trace_processor_context.h"
45 
46 namespace perfetto {
47 namespace trace_processor {
48 namespace {
49 
50 using ::perfetto::protos::pbzero::InternedV8Isolate;
51 using ::perfetto::protos::pbzero::InternedV8JsFunction;
52 using ::perfetto::protos::pbzero::InternedV8JsScript;
53 using ::perfetto::protos::pbzero::InternedV8WasmScript;
54 using ::perfetto::protos::pbzero::V8InternalCode;
55 using ::perfetto::protos::pbzero::V8JsCode;
56 using ::perfetto::protos::pbzero::V8RegExpCode;
57 using ::perfetto::protos::pbzero::V8String;
58 using ::perfetto::protos::pbzero::V8WasmCode;
59 
IsInterpretedCode(const V8JsCode::Decoder & code)60 bool IsInterpretedCode(const V8JsCode::Decoder& code) {
61   switch (code.tier()) {
62     case V8JsCode::TIER_IGNITION:
63       return true;
64 
65     case V8JsCode::TIER_UNKNOWN:
66     case V8JsCode::TIER_SPARKPLUG:
67     case V8JsCode::TIER_MAGLEV:
68     case V8JsCode::TIER_TURBOSHAFT:
69     case V8JsCode::TIER_TURBOFAN:
70       return false;
71   }
72   PERFETTO_FATAL("Unreachable");
73 }
74 
IsNativeCode(const V8JsCode::Decoder & code)75 bool IsNativeCode(const V8JsCode::Decoder& code) {
76   switch (code.tier()) {
77     case V8JsCode::TIER_UNKNOWN:
78     case V8JsCode::TIER_IGNITION:
79       return false;
80 
81     case V8JsCode::TIER_SPARKPLUG:
82     case V8JsCode::TIER_MAGLEV:
83     case V8JsCode::TIER_TURBOSHAFT:
84     case V8JsCode::TIER_TURBOFAN:
85       return true;
86   }
87   PERFETTO_FATAL("Unreachable");
88 }
89 
JsScriptTypeToString(int32_t type)90 base::StringView JsScriptTypeToString(int32_t type) {
91   if (type < protos::pbzero::InternedV8JsScript_Type_MIN ||
92       type > protos::pbzero::InternedV8JsScript_Type_MAX) {
93     return "UNKNOWN";
94   }
95   base::StringView name =
96       InternedV8JsScript::Type_Name(InternedV8JsScript::Type(type));
97   // Remove the "TYPE_" prefix
98   return name.substr(5);
99 }
100 
JsFunctionKindToString(int32_t kind)101 base::StringView JsFunctionKindToString(int32_t kind) {
102   if (kind < protos::pbzero::InternedV8JsFunction_Kind_MIN ||
103       kind > protos::pbzero::InternedV8JsFunction_Kind_MAX) {
104     return "UNKNOWN";
105   }
106   base::StringView name =
107       InternedV8JsFunction::Kind_Name(InternedV8JsFunction::Kind(kind));
108   // Remove the "KIND_" prefix
109   return name.substr(5);
110 }
111 
JsCodeTierToString(int32_t tier)112 base::StringView JsCodeTierToString(int32_t tier) {
113   if (tier < protos::pbzero::V8JsCode_Tier_MIN ||
114       tier > protos::pbzero::V8JsCode_Tier_MAX) {
115     return "UNKNOWN";
116   }
117   base::StringView name = V8JsCode::Tier_Name(V8JsCode::Tier(tier));
118   // Remove the "TIER_" prefix
119   return name.substr(5);
120 }
121 
InternalCodeTypeToString(int32_t type)122 base::StringView InternalCodeTypeToString(int32_t type) {
123   if (type < protos::pbzero::V8InternalCode_Type_MIN ||
124       type > protos::pbzero::V8InternalCode_Type_MAX) {
125     return "UNKNOWN";
126   }
127   base::StringView name = V8InternalCode::Type_Name(V8InternalCode::Type(type));
128   // Remove the "TYPE_" prefix
129   return name.substr(5);
130 }
131 
WasmCodeTierToString(int32_t tier)132 base::StringView WasmCodeTierToString(int32_t tier) {
133   if (tier < protos::pbzero::V8WasmCode_Tier_MIN ||
134       tier > protos::pbzero::V8WasmCode_Tier_MAX) {
135     return "UNKNOWN";
136   }
137   base::StringView name = V8WasmCode::Tier_Name(V8WasmCode::Tier(tier));
138   // Remove the "TIER_" prefix
139   return name.substr(5);
140 }
141 
142 }  // namespace
143 
V8Tracker(TraceProcessorContext * context)144 V8Tracker::V8Tracker(TraceProcessorContext* context) : context_(context) {}
145 
146 V8Tracker::~V8Tracker() = default;
147 
InternIsolate(protozero::ConstBytes bytes)148 std::optional<IsolateId> V8Tracker::InternIsolate(protozero::ConstBytes bytes) {
149   InternedV8Isolate::Decoder isolate(bytes);
150 
151   const IsolateKey isolate_key{
152       context_->process_tracker->GetOrCreateProcess(isolate.pid()),
153       isolate.isolate_id()};
154 
155   if (auto* id = isolate_index_.Find(isolate_key); id) {
156     return *id;
157   }
158 
159   // TODO(b/347250452): Implement support for no code range
160   if (!isolate.has_code_range()) {
161     context_->storage->IncrementStats(stats::v8_isolate_has_no_code_range);
162     isolate_index_.Insert(isolate_key, std::nullopt);
163     return std::nullopt;
164   }
165 
166   return *isolate_index_.Insert(isolate_key, CreateIsolate(isolate)).first;
167 }
168 
FindEmbeddedBlobMapping(UniquePid upid,AddressRange embedded_blob_code) const169 UserMemoryMapping* V8Tracker::FindEmbeddedBlobMapping(
170     UniquePid upid,
171     AddressRange embedded_blob_code) const {
172   UserMemoryMapping* m = context_->mapping_tracker->FindUserMappingForAddress(
173       upid, embedded_blob_code.start());
174   if (!m) {
175     return nullptr;
176   }
177 
178   if (m->memory_range().start() == embedded_blob_code.start() &&
179       embedded_blob_code.end() <= m->memory_range().end()) {
180     return m;
181   }
182 
183   return nullptr;
184 }
185 
GetIsolateCodeRanges(UniquePid upid,const protos::pbzero::InternedV8Isolate::Decoder & isolate)186 std::pair<V8Tracker::IsolateCodeRanges, bool> V8Tracker::GetIsolateCodeRanges(
187     UniquePid upid,
188     const protos::pbzero::InternedV8Isolate::Decoder& isolate) {
189   PERFETTO_CHECK(isolate.has_code_range());
190 
191   IsolateCodeRanges res;
192 
193   InternedV8Isolate::CodeRange::Decoder code_range_proto(isolate.code_range());
194   AddressRange code_range = AddressRange::FromStartAndSize(
195       code_range_proto.base_address(), code_range_proto.size());
196 
197   res.heap_code.Add(code_range);
198   if (isolate.has_embedded_blob_code_start_address() &&
199       isolate.embedded_blob_code_size() != 0) {
200     res.embedded_blob = AddressRange::FromStartAndSize(
201         isolate.embedded_blob_code_start_address(),
202         isolate.embedded_blob_code_size());
203 
204     if (UserMemoryMapping* m =
205             FindEmbeddedBlobMapping(upid, *res.embedded_blob);
206         m) {
207       res.embedded_blob = m->memory_range();
208     }
209 
210     res.heap_code.Remove(*res.embedded_blob);
211   }
212 
213   return {std::move(res), code_range_proto.is_process_wide()};
214 }
215 
CreateJitCaches(UniquePid upid,const IsolateCodeRanges & code_ranges)216 AddressRangeMap<JitCache*> V8Tracker::CreateJitCaches(
217     UniquePid upid,
218     const IsolateCodeRanges& code_ranges) {
219   JitTracker* const jit_tracker = JitTracker::GetOrCreate(context_);
220   AddressRangeMap<JitCache*> jit_caches;
221   for (const AddressRange& r : code_ranges.heap_code) {
222     jit_caches.Emplace(r, jit_tracker->CreateJitCache("v8 code", upid, r));
223   }
224   if (code_ranges.embedded_blob) {
225     jit_caches.Emplace(*code_ranges.embedded_blob,
226                        jit_tracker->CreateJitCache("v8 blob", upid,
227                                                    *code_ranges.embedded_blob));
228   }
229 
230   return jit_caches;
231 }
232 
GetOrCreateSharedJitCaches(UniquePid upid,const IsolateCodeRanges & code_ranges)233 AddressRangeMap<JitCache*> V8Tracker::GetOrCreateSharedJitCaches(
234     UniquePid upid,
235     const IsolateCodeRanges& code_ranges) {
236   if (auto* shared = shared_code_ranges_.Find(upid); shared) {
237     PERFETTO_CHECK(shared->code_ranges == code_ranges);
238     return shared->jit_caches;
239   }
240 
241   return shared_code_ranges_
242       .Insert(upid, {code_ranges, CreateJitCaches(upid, code_ranges)})
243       .first->jit_caches;
244 }
245 
CreateIsolate(const InternedV8Isolate::Decoder & isolate_proto)246 IsolateId V8Tracker::CreateIsolate(
247     const InternedV8Isolate::Decoder& isolate_proto) {
248   auto v8_isolate = InsertIsolate(isolate_proto);
249   const UniquePid upid = v8_isolate.upid();
250 
251   auto [code_ranges, is_process_wide] =
252       GetIsolateCodeRanges(upid, isolate_proto);
253 
254   PERFETTO_CHECK(isolates_
255                      .Insert(v8_isolate.id(),
256                              is_process_wide
257                                  ? GetOrCreateSharedJitCaches(upid, code_ranges)
258                                  : CreateJitCaches(upid, code_ranges))
259                      .second);
260 
261   return v8_isolate.id();
262 }
263 
InsertIsolate(const InternedV8Isolate::Decoder & isolate)264 tables::V8IsolateTable::ConstRowReference V8Tracker::InsertIsolate(
265     const InternedV8Isolate::Decoder& isolate) {
266   InternedV8Isolate::CodeRange::Decoder code_range(isolate.code_range());
267   return context_->storage->mutable_v8_isolate_table()
268       ->Insert(
269           {context_->process_tracker->GetOrCreateProcess(isolate.pid()),
270            isolate.isolate_id(),
271            static_cast<int64_t>(isolate.embedded_blob_code_start_address()),
272            static_cast<int64_t>(isolate.embedded_blob_code_size()),
273            static_cast<int64_t>(code_range.base_address()),
274            static_cast<int64_t>(code_range.size()),
275            code_range.is_process_wide(),
276            code_range.has_embedded_blob_code_copy_start_address()
277                ? std::make_optional(static_cast<int64_t>(
278                      code_range.embedded_blob_code_copy_start_address()))
279                : std::nullopt})
280       .row_reference;
281 }
282 
InternJsScript(protozero::ConstBytes bytes,IsolateId isolate_id)283 tables::V8JsScriptTable::Id V8Tracker::InternJsScript(
284     protozero::ConstBytes bytes,
285     IsolateId isolate_id) {
286   InternedV8JsScript::Decoder script(bytes);
287 
288   if (auto* id =
289           js_script_index_.Find(std::make_pair(isolate_id, script.script_id()));
290       id) {
291     return *id;
292   }
293 
294   tables::V8JsScriptTable::Row row;
295   row.v8_isolate_id = isolate_id;
296   row.internal_script_id = script.script_id();
297   row.script_type =
298       context_->storage->InternString(JsScriptTypeToString(script.type()));
299   row.name = InternV8String(V8String::Decoder(script.name()));
300   row.source = InternV8String(V8String::Decoder(script.source()));
301 
302   tables::V8JsScriptTable::Id script_id =
303       context_->storage->mutable_v8_js_script_table()->Insert(row).id;
304   js_script_index_.Insert(std::make_pair(isolate_id, script.script_id()),
305                           script_id);
306   return script_id;
307 }
308 
InternWasmScript(protozero::ConstBytes bytes,IsolateId isolate_id)309 tables::V8WasmScriptTable::Id V8Tracker::InternWasmScript(
310     protozero::ConstBytes bytes,
311     IsolateId isolate_id) {
312   InternedV8WasmScript::Decoder script(bytes);
313 
314   if (auto* id = wasm_script_index_.Find(
315           std::make_pair(isolate_id, script.script_id()));
316       id) {
317     return *id;
318   }
319 
320   tables::V8WasmScriptTable::Row row;
321   row.v8_isolate_id = isolate_id;
322   row.internal_script_id = script.script_id();
323   row.url = context_->storage->InternString(script.url());
324 
325   tables::V8WasmScriptTable::Id script_id =
326       context_->storage->mutable_v8_wasm_script_table()->Insert(row).id;
327   wasm_script_index_.Insert(std::make_pair(isolate_id, script.script_id()),
328                             script_id);
329   return script_id;
330 }
331 
InternJsFunction(protozero::ConstBytes bytes,StringId name,tables::V8JsScriptTable::Id script_id)332 tables::V8JsFunctionTable::Id V8Tracker::InternJsFunction(
333     protozero::ConstBytes bytes,
334     StringId name,
335     tables::V8JsScriptTable::Id script_id) {
336   InternedV8JsFunction::Decoder function(bytes);
337 
338   tables::V8JsFunctionTable::Row row;
339   row.name = name;
340   row.v8_js_script_id = script_id;
341   row.is_toplevel = function.is_toplevel();
342   row.kind =
343       context_->storage->InternString(JsFunctionKindToString(function.kind()));
344   // TODO(carlscab): Line and column are hard. Offset is in bytes, line and
345   // column are in characters and we potentially have a multi byte encoding
346   // (UTF16). Good luck!
347   if (function.has_byte_offset()) {
348     row.line = 1;
349     row.col = function.byte_offset();
350   }
351 
352   if (auto* id = js_function_index_.Find(row); id) {
353     return *id;
354   }
355 
356   tables::V8JsFunctionTable::Id function_id =
357       context_->storage->mutable_v8_js_function_table()->Insert(row).id;
358   js_function_index_.Insert(row, function_id);
359   return function_id;
360 }
361 
MaybeFindJitCache(IsolateId isolate_id,AddressRange code_range) const362 JitCache* V8Tracker::MaybeFindJitCache(IsolateId isolate_id,
363                                        AddressRange code_range) const {
364   if (code_range.empty()) {
365     context_->storage->IncrementStats(stats::v8_code_load_missing_code_range);
366     return nullptr;
367   }
368   auto* isolate = isolates_.Find(isolate_id);
369   PERFETTO_CHECK(isolate);
370   if (auto it = isolate->FindRangeThatContains(code_range);
371       it != isolate->end()) {
372     return it->second;
373   }
374 
375   return nullptr;
376 }
377 
FindJitCache(IsolateId isolate_id,AddressRange code_range) const378 JitCache* V8Tracker::FindJitCache(IsolateId isolate_id,
379                                   AddressRange code_range) const {
380   if (code_range.empty()) {
381     context_->storage->IncrementStats(stats::v8_code_load_missing_code_range);
382     return nullptr;
383   }
384   JitCache* cache = MaybeFindJitCache(isolate_id, code_range);
385   if (!cache) {
386     context_->storage->IncrementStats(stats::v8_no_code_range);
387   }
388   return cache;
389 }
390 
AddJsCode(int64_t timestamp,UniqueTid utid,IsolateId isolate_id,tables::V8JsFunctionTable::Id function_id,const V8JsCode::Decoder & code)391 void V8Tracker::AddJsCode(int64_t timestamp,
392                           UniqueTid utid,
393                           IsolateId isolate_id,
394                           tables::V8JsFunctionTable::Id function_id,
395                           const V8JsCode::Decoder& code) {
396   const StringId tier =
397       context_->storage->InternString(JsCodeTierToString(code.tier()));
398 
399   const AddressRange code_range = AddressRange::FromStartAndSize(
400       code.instruction_start(), code.instruction_size_bytes());
401 
402   JitCache* jit_cache = nullptr;
403 
404   if (IsInterpretedCode(code)) {
405     // If --interpreted_frames_native_stack is specified interpreted frames will
406     // also be emitted as native functions.
407     // TODO(carlscab): Add an additional tier to for NATIVE_IGNITION_FRAME. Int
408     // he meantime we can infer that this is the case if we have a hit in the
409     // jit cache. NOte we call MaybeFindJitCache to not log an error if there is
410     // no hit.
411     jit_cache = MaybeFindJitCache(isolate_id, code_range);
412     if (!jit_cache) {
413       context_->storage->mutable_v8_js_code_table()->Insert(
414           {std::nullopt, function_id, tier,
415            context_->storage->InternString(base::StringView(base::Base64Encode(
416                code.bytecode().data, code.bytecode().size)))});
417       return;
418     }
419   } else if (IsNativeCode(code)) {
420     jit_cache = FindJitCache(isolate_id, code_range);
421   } else {
422     context_->storage->IncrementStats(stats::v8_unknown_code_type);
423     return;
424   }
425 
426   if (!jit_cache) {
427     return;
428   }
429 
430   auto function =
431       *context_->storage->v8_js_function_table().FindById(function_id);
432   auto script = *context_->storage->v8_js_script_table().FindById(
433       function.v8_js_script_id());
434   const auto jit_code_id = jit_cache->LoadCode(
435       timestamp, utid, code_range, function.name(),
436       JitCache::SourceLocation{script.name(), function.line().value_or(0)},
437       code.has_machine_code()
438           ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
439                                               code.machine_code().size))
440           : TraceBlobView());
441 
442   context_->storage->mutable_v8_js_code_table()->Insert(
443       {jit_code_id, function_id, tier});
444 }
445 
AddInternalCode(int64_t timestamp,UniqueTid utid,IsolateId isolate_id,const V8InternalCode::Decoder & code)446 void V8Tracker::AddInternalCode(int64_t timestamp,
447                                 UniqueTid utid,
448                                 IsolateId isolate_id,
449                                 const V8InternalCode::Decoder& code) {
450   const AddressRange code_range = AddressRange::FromStartAndSize(
451       code.instruction_start(), code.instruction_size_bytes());
452   JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
453   if (!jit_cache) {
454     return;
455   }
456 
457   const StringId function_name = context_->storage->InternString(code.name());
458   const StringId type =
459       context_->storage->InternString(InternalCodeTypeToString(code.type()));
460   const auto jit_code_id = jit_cache->LoadCode(
461       timestamp, utid, code_range, function_name, std::nullopt,
462       code.has_machine_code()
463           ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
464                                               code.machine_code().size))
465           : TraceBlobView());
466 
467   context_->storage->mutable_v8_internal_code_table()->Insert(
468       {jit_code_id, isolate_id, function_name, type});
469 }
470 
AddWasmCode(int64_t timestamp,UniqueTid utid,IsolateId isolate_id,tables::V8WasmScriptTable::Id script_id,const V8WasmCode::Decoder & code)471 void V8Tracker::AddWasmCode(int64_t timestamp,
472                             UniqueTid utid,
473                             IsolateId isolate_id,
474                             tables::V8WasmScriptTable::Id script_id,
475                             const V8WasmCode::Decoder& code) {
476   const AddressRange code_range = AddressRange::FromStartAndSize(
477       code.instruction_start(), code.instruction_size_bytes());
478   JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
479   if (!jit_cache) {
480     return;
481   }
482 
483   const StringId function_name =
484       context_->storage->InternString(code.function_name());
485   const StringId tier =
486       context_->storage->InternString(WasmCodeTierToString(code.tier()));
487 
488   const auto jit_code_id = jit_cache->LoadCode(
489       timestamp, utid, code_range, function_name, std::nullopt,
490       code.has_machine_code()
491           ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
492                                               code.machine_code().size))
493           : TraceBlobView());
494 
495   context_->storage->mutable_v8_wasm_code_table()->Insert(
496       {jit_code_id, isolate_id, script_id, function_name, tier});
497 }
498 
AddRegExpCode(int64_t timestamp,UniqueTid utid,IsolateId isolate_id,const V8RegExpCode::Decoder & code)499 void V8Tracker::AddRegExpCode(int64_t timestamp,
500                               UniqueTid utid,
501                               IsolateId isolate_id,
502                               const V8RegExpCode::Decoder& code) {
503   const AddressRange code_range = AddressRange::FromStartAndSize(
504       code.instruction_start(), code.instruction_size_bytes());
505   JitCache* const jit_cache = FindJitCache(isolate_id, code_range);
506   if (!jit_cache) {
507     return;
508   }
509 
510   const StringId function_name = context_->storage->InternString("[RegExp]");
511   const StringId pattern = InternV8String(V8String::Decoder(code.pattern()));
512   const auto jit_code_id = jit_cache->LoadCode(
513       timestamp, utid, code_range, function_name, std::nullopt,
514       code.has_machine_code()
515           ? TraceBlobView(TraceBlob::CopyFrom(code.machine_code().data,
516                                               code.machine_code().size))
517           : TraceBlobView());
518 
519   context_->storage->mutable_v8_regexp_code_table()->Insert(
520       {jit_code_id, isolate_id, pattern});
521 }
522 
InternV8String(const V8String::Decoder & v8_string)523 StringId V8Tracker::InternV8String(const V8String::Decoder& v8_string) {
524   auto& storage = *context_->storage;
525   if (v8_string.has_latin1()) {
526     return storage.InternString(
527         base::StringView(ConvertLatin1ToUtf8(v8_string.latin1())));
528   }
529 
530   if (v8_string.has_utf16_le()) {
531     return storage.InternString(
532         base::StringView(ConvertUtf16LeToUtf8(v8_string.utf16_le())));
533   }
534 
535   if (v8_string.has_utf16_be()) {
536     return storage.InternString(
537         base::StringView(ConvertUtf16BeToUtf8(v8_string.utf16_be())));
538   }
539   return storage.InternString("");
540 }
541 
542 }  // namespace trace_processor
543 }  // namespace perfetto
544