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