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