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