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