1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28 #include "src/diagnostics/perf-jit.h"
29
30 #include "src/common/assert-scope.h"
31
32 // Only compile the {PerfJitLogger} on Linux.
33 #if V8_OS_LINUX
34
35 #include <fcntl.h>
36 #include <sys/mman.h>
37 #include <unistd.h>
38
39 #include <memory>
40
41 #include "src/base/platform/wrappers.h"
42 #include "src/codegen/assembler.h"
43 #include "src/codegen/source-position-table.h"
44 #include "src/diagnostics/eh-frame.h"
45 #include "src/objects/objects-inl.h"
46 #include "src/objects/shared-function-info.h"
47 #include "src/snapshot/embedded/embedded-data.h"
48 #include "src/utils/ostreams.h"
49
50 #if V8_ENABLE_WEBASSEMBLY
51 #include "src/wasm/wasm-code-manager.h"
52 #endif // V8_ENABLE_WEBASSEMBLY
53
54 namespace v8 {
55 namespace internal {
56
57 struct PerfJitHeader {
58 uint32_t magic_;
59 uint32_t version_;
60 uint32_t size_;
61 uint32_t elf_mach_target_;
62 uint32_t reserved_;
63 uint32_t process_id_;
64 uint64_t time_stamp_;
65 uint64_t flags_;
66
67 static const uint32_t kMagic = 0x4A695444;
68 static const uint32_t kVersion = 1;
69 };
70
71 struct PerfJitBase {
72 enum PerfJitEvent {
73 kLoad = 0,
74 kMove = 1,
75 kDebugInfo = 2,
76 kClose = 3,
77 kUnwindingInfo = 4
78 };
79
80 uint32_t event_;
81 uint32_t size_;
82 uint64_t time_stamp_;
83 };
84
85 struct PerfJitCodeLoad : PerfJitBase {
86 uint32_t process_id_;
87 uint32_t thread_id_;
88 uint64_t vma_;
89 uint64_t code_address_;
90 uint64_t code_size_;
91 uint64_t code_id_;
92 };
93
94 struct PerfJitDebugEntry {
95 uint64_t address_;
96 int line_number_;
97 int column_;
98 // Followed by null-terminated name or \0xFF\0 if same as previous.
99 };
100
101 struct PerfJitCodeDebugInfo : PerfJitBase {
102 uint64_t address_;
103 uint64_t entry_count_;
104 // Followed by entry_count_ instances of PerfJitDebugEntry.
105 };
106
107 struct PerfJitCodeUnwindingInfo : PerfJitBase {
108 uint64_t unwinding_size_;
109 uint64_t eh_frame_hdr_size_;
110 uint64_t mapped_size_;
111 // Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data.
112 };
113
114 const char PerfJitLogger::kFilenameFormatString[] = "./jit-%d.dump";
115
116 // Extra padding for the PID in the filename
117 const int PerfJitLogger::kFilenameBufferPadding = 16;
118
119 static const char kStringTerminator[] = {'\0'};
120 static const char kRepeatedNameMarker[] = {'\xff', '\0'};
121
122 base::LazyRecursiveMutex PerfJitLogger::file_mutex_;
123 // The following static variables are protected by PerfJitLogger::file_mutex_.
124 int PerfJitLogger::process_id_ = 0;
125 uint64_t PerfJitLogger::reference_count_ = 0;
126 void* PerfJitLogger::marker_address_ = nullptr;
127 uint64_t PerfJitLogger::code_index_ = 0;
128 FILE* PerfJitLogger::perf_output_handle_ = nullptr;
129
OpenJitDumpFile()130 void PerfJitLogger::OpenJitDumpFile() {
131 // Open the perf JIT dump file.
132 perf_output_handle_ = nullptr;
133
134 int bufferSize = sizeof(kFilenameFormatString) + kFilenameBufferPadding;
135 base::ScopedVector<char> perf_dump_name(bufferSize);
136 int size = SNPrintF(perf_dump_name, kFilenameFormatString, process_id_);
137 CHECK_NE(size, -1);
138
139 int fd = open(perf_dump_name.begin(), O_CREAT | O_TRUNC | O_RDWR, 0666);
140 if (fd == -1) return;
141
142 // If --perf-prof-delete-file is given, unlink the file right after opening
143 // it. This keeps the file handle to the file valid. This only works on Linux,
144 // which is the only platform supported for --perf-prof anyway.
145 if (FLAG_perf_prof_delete_file) CHECK_EQ(0, unlink(perf_dump_name.begin()));
146
147 marker_address_ = OpenMarkerFile(fd);
148 if (marker_address_ == nullptr) return;
149
150 perf_output_handle_ = fdopen(fd, "w+");
151 if (perf_output_handle_ == nullptr) return;
152
153 setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
154 }
155
CloseJitDumpFile()156 void PerfJitLogger::CloseJitDumpFile() {
157 if (perf_output_handle_ == nullptr) return;
158 base::Fclose(perf_output_handle_);
159 perf_output_handle_ = nullptr;
160 }
161
OpenMarkerFile(int fd)162 void* PerfJitLogger::OpenMarkerFile(int fd) {
163 long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
164 if (page_size == -1) return nullptr;
165
166 // Mmap the file so that there is a mmap record in the perf_data file.
167 //
168 // The map must be PROT_EXEC to ensure it is not ignored by perf record.
169 void* marker_address =
170 mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
171 return (marker_address == MAP_FAILED) ? nullptr : marker_address;
172 }
173
CloseMarkerFile(void * marker_address)174 void PerfJitLogger::CloseMarkerFile(void* marker_address) {
175 if (marker_address == nullptr) return;
176 long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
177 if (page_size == -1) return;
178 munmap(marker_address, page_size);
179 }
180
PerfJitLogger(Isolate * isolate)181 PerfJitLogger::PerfJitLogger(Isolate* isolate) : CodeEventLogger(isolate) {
182 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
183 process_id_ = base::OS::GetCurrentProcessId();
184
185 reference_count_++;
186 // If this is the first logger, open the file and write the header.
187 if (reference_count_ == 1) {
188 OpenJitDumpFile();
189 if (perf_output_handle_ == nullptr) return;
190 LogWriteHeader();
191 }
192 }
193
~PerfJitLogger()194 PerfJitLogger::~PerfJitLogger() {
195 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
196
197 reference_count_--;
198 // If this was the last logger, close the file.
199 if (reference_count_ == 0) {
200 CloseJitDumpFile();
201 }
202 }
203
GetTimestamp()204 uint64_t PerfJitLogger::GetTimestamp() {
205 struct timespec ts;
206 int result = clock_gettime(CLOCK_MONOTONIC, &ts);
207 DCHECK_EQ(0, result);
208 USE(result);
209 static const uint64_t kNsecPerSec = 1000000000;
210 return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
211 }
212
LogRecordedBuffer(Handle<AbstractCode> abstract_code,MaybeHandle<SharedFunctionInfo> maybe_shared,const char * name,int length)213 void PerfJitLogger::LogRecordedBuffer(
214 Handle<AbstractCode> abstract_code,
215 MaybeHandle<SharedFunctionInfo> maybe_shared, const char* name,
216 int length) {
217 if (FLAG_perf_basic_prof_only_functions &&
218 (abstract_code->kind() != CodeKind::INTERPRETED_FUNCTION &&
219 abstract_code->kind() != CodeKind::TURBOFAN &&
220 abstract_code->kind() != CodeKind::MAGLEV &&
221 abstract_code->kind() != CodeKind::BASELINE)) {
222 return;
223 }
224
225 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
226
227 if (perf_output_handle_ == nullptr) return;
228
229 // We only support non-interpreted functions.
230 if (!abstract_code->IsCode()) return;
231 Handle<Code> code = Handle<Code>::cast(abstract_code);
232 DCHECK(code->raw_instruction_start() == code->address() + Code::kHeaderSize);
233
234 // Debug info has to be emitted first.
235 Handle<SharedFunctionInfo> shared;
236 if (FLAG_perf_prof && maybe_shared.ToHandle(&shared)) {
237 // TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
238 if (code->kind() != CodeKind::JS_TO_WASM_FUNCTION &&
239 code->kind() != CodeKind::WASM_TO_JS_FUNCTION) {
240 LogWriteDebugInfo(code, shared);
241 }
242 }
243
244 const char* code_name = name;
245 uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->InstructionStart());
246
247 // Unwinding info comes right after debug info.
248 if (FLAG_perf_prof_unwinding_info) LogWriteUnwindingInfo(*code);
249
250 WriteJitCodeLoadEntry(code_pointer, code->InstructionSize(), code_name,
251 length);
252 }
253
254 #if V8_ENABLE_WEBASSEMBLY
LogRecordedBuffer(const wasm::WasmCode * code,const char * name,int length)255 void PerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code,
256 const char* name, int length) {
257 base::LockGuard<base::RecursiveMutex> guard_file(file_mutex_.Pointer());
258
259 if (perf_output_handle_ == nullptr) return;
260
261 if (FLAG_perf_prof_annotate_wasm) LogWriteDebugInfo(code);
262
263 WriteJitCodeLoadEntry(code->instructions().begin(),
264 code->instructions().length(), name, length);
265 }
266 #endif // V8_ENABLE_WEBASSEMBLY
267
WriteJitCodeLoadEntry(const uint8_t * code_pointer,uint32_t code_size,const char * name,int name_length)268 void PerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer,
269 uint32_t code_size, const char* name,
270 int name_length) {
271 PerfJitCodeLoad code_load;
272 code_load.event_ = PerfJitCodeLoad::kLoad;
273 code_load.size_ = sizeof(code_load) + name_length + 1 + code_size;
274 code_load.time_stamp_ = GetTimestamp();
275 code_load.process_id_ = static_cast<uint32_t>(process_id_);
276 code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
277 code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
278 code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
279 code_load.code_size_ = code_size;
280 code_load.code_id_ = code_index_;
281
282 code_index_++;
283
284 LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load));
285 LogWriteBytes(name, name_length);
286 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
287 LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
288 }
289
290 namespace {
291
292 constexpr char kUnknownScriptNameString[] = "<unknown>";
293 constexpr size_t kUnknownScriptNameStringLen =
294 arraysize(kUnknownScriptNameString) - 1;
295
296 namespace {
GetScriptName(Object maybeScript,std::unique_ptr<char[]> * storage,const DisallowGarbageCollection & no_gc)297 base::Vector<const char> GetScriptName(Object maybeScript,
298 std::unique_ptr<char[]>* storage,
299 const DisallowGarbageCollection& no_gc) {
300 if (maybeScript.IsScript()) {
301 Object name_or_url = Script::cast(maybeScript).GetNameOrSourceURL();
302 if (name_or_url.IsSeqOneByteString()) {
303 SeqOneByteString str = SeqOneByteString::cast(name_or_url);
304 return {reinterpret_cast<char*>(str.GetChars(no_gc)),
305 static_cast<size_t>(str.length())};
306 } else if (name_or_url.IsString()) {
307 int length;
308 *storage = String::cast(name_or_url)
309 .ToCString(DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
310 return {storage->get(), static_cast<size_t>(length)};
311 }
312 }
313 return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
314 }
315
316 } // namespace
317
GetSourcePositionInfo(Handle<Code> code,Handle<SharedFunctionInfo> function,SourcePosition pos)318 SourcePositionInfo GetSourcePositionInfo(Handle<Code> code,
319 Handle<SharedFunctionInfo> function,
320 SourcePosition pos) {
321 DisallowGarbageCollection disallow;
322 if (code->is_turbofanned()) {
323 return pos.FirstInfo(code);
324 } else {
325 return SourcePositionInfo(pos, function);
326 }
327 }
328
329 } // namespace
330
LogWriteDebugInfo(Handle<Code> code,Handle<SharedFunctionInfo> shared)331 void PerfJitLogger::LogWriteDebugInfo(Handle<Code> code,
332 Handle<SharedFunctionInfo> shared) {
333 // Line ends of all scripts have been initialized prior to this.
334 DisallowGarbageCollection no_gc;
335 // The WasmToJS wrapper stubs have source position entries.
336 if (!shared->HasSourceCode()) return;
337
338 PerfJitCodeDebugInfo debug_info;
339 uint32_t size = sizeof(debug_info);
340
341 ByteArray source_position_table = code->SourcePositionTable(*shared);
342 // Compute the entry count and get the names of all scripts.
343 // Avoid additional work if the script name is repeated. Multiple script
344 // names only occur for cross-script inlining.
345 uint32_t entry_count = 0;
346 Object last_script = Smi::zero();
347 std::vector<base::Vector<const char>> script_names;
348 for (SourcePositionTableIterator iterator(source_position_table);
349 !iterator.done(); iterator.Advance()) {
350 SourcePositionInfo info(
351 GetSourcePositionInfo(code, shared, iterator.source_position()));
352 Object current_script = *info.script;
353 if (current_script != last_script) {
354 std::unique_ptr<char[]> name_storage;
355 auto name = GetScriptName(shared->script(), &name_storage, no_gc);
356 script_names.push_back(name);
357 // Add the size of the name after each entry.
358 size += name.size() + sizeof(kStringTerminator);
359 last_script = current_script;
360 } else {
361 size += sizeof(kRepeatedNameMarker);
362 }
363 entry_count++;
364 }
365 if (entry_count == 0) return;
366
367 debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
368 debug_info.time_stamp_ = GetTimestamp();
369 debug_info.address_ = code->InstructionStart();
370 debug_info.entry_count_ = entry_count;
371
372 // Add the sizes of fixed parts of entries.
373 size += entry_count * sizeof(PerfJitDebugEntry);
374
375 int padding = ((size + 7) & (~7)) - size;
376 debug_info.size_ = size + padding;
377 LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
378
379 Address code_start = code->InstructionStart();
380
381 last_script = Smi::zero();
382 int script_names_index = 0;
383 for (SourcePositionTableIterator iterator(source_position_table);
384 !iterator.done(); iterator.Advance()) {
385 SourcePositionInfo info(
386 GetSourcePositionInfo(code, shared, iterator.source_position()));
387 PerfJitDebugEntry entry;
388 // The entry point of the function will be placed straight after the ELF
389 // header when processed by "perf inject". Adjust the position addresses
390 // accordingly.
391 entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
392 entry.line_number_ = info.line + 1;
393 entry.column_ = info.column + 1;
394 LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
395 Object current_script = *info.script;
396 if (current_script != last_script) {
397 auto name_string = script_names[script_names_index];
398 LogWriteBytes(name_string.begin(),
399 static_cast<uint32_t>(name_string.size()));
400 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
401 script_names_index++;
402 last_script = current_script;
403 } else {
404 // Use the much shorter kRepeatedNameMarker for repeated names.
405 LogWriteBytes(kRepeatedNameMarker, sizeof(kRepeatedNameMarker));
406 }
407 }
408 char padding_bytes[8] = {0};
409 LogWriteBytes(padding_bytes, padding);
410 }
411
412 #if V8_ENABLE_WEBASSEMBLY
LogWriteDebugInfo(const wasm::WasmCode * code)413 void PerfJitLogger::LogWriteDebugInfo(const wasm::WasmCode* code) {
414 wasm::WasmModuleSourceMap* source_map =
415 code->native_module()->GetWasmSourceMap();
416 wasm::WireBytesRef code_ref =
417 code->native_module()->module()->functions[code->index()].code;
418 uint32_t code_offset = code_ref.offset();
419 uint32_t code_end_offset = code_ref.end_offset();
420
421 uint32_t entry_count = 0;
422 uint32_t size = 0;
423
424 if (!source_map || !source_map->IsValid() ||
425 !source_map->HasSource(code_offset, code_end_offset)) {
426 return;
427 }
428
429 for (SourcePositionTableIterator iterator(code->source_positions());
430 !iterator.done(); iterator.Advance()) {
431 uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
432 if (!source_map->HasValidEntry(code_offset, offset)) continue;
433 entry_count++;
434 size += source_map->GetFilename(offset).size() + 1;
435 }
436
437 if (entry_count == 0) return;
438
439 PerfJitCodeDebugInfo debug_info;
440
441 debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
442 debug_info.time_stamp_ = GetTimestamp();
443 debug_info.address_ =
444 reinterpret_cast<uintptr_t>(code->instructions().begin());
445 debug_info.entry_count_ = entry_count;
446
447 size += sizeof(debug_info);
448 // Add the sizes of fixed parts of entries.
449 size += entry_count * sizeof(PerfJitDebugEntry);
450
451 int padding = ((size + 7) & (~7)) - size;
452 debug_info.size_ = size + padding;
453 LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
454
455 uintptr_t code_begin =
456 reinterpret_cast<uintptr_t>(code->instructions().begin());
457
458 for (SourcePositionTableIterator iterator(code->source_positions());
459 !iterator.done(); iterator.Advance()) {
460 uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
461 if (!source_map->HasValidEntry(code_offset, offset)) continue;
462 PerfJitDebugEntry entry;
463 // The entry point of the function will be placed straight after the ELF
464 // header when processed by "perf inject". Adjust the position addresses
465 // accordingly.
466 entry.address_ = code_begin + iterator.code_offset() + kElfHeaderSize;
467 entry.line_number_ =
468 static_cast<int>(source_map->GetSourceLine(offset)) + 1;
469 entry.column_ = 1;
470 LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
471 std::string name_string = source_map->GetFilename(offset);
472 LogWriteBytes(name_string.c_str(), static_cast<int>(name_string.size()));
473 LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
474 }
475
476 char padding_bytes[8] = {0};
477 LogWriteBytes(padding_bytes, padding);
478 }
479 #endif // V8_ENABLE_WEBASSEMBLY
480
LogWriteUnwindingInfo(Code code)481 void PerfJitLogger::LogWriteUnwindingInfo(Code code) {
482 PerfJitCodeUnwindingInfo unwinding_info_header;
483 unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
484 unwinding_info_header.time_stamp_ = GetTimestamp();
485 unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
486
487 if (code.has_unwinding_info()) {
488 unwinding_info_header.unwinding_size_ = code.unwinding_info_size();
489 unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
490 } else {
491 unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
492 unwinding_info_header.mapped_size_ = 0;
493 }
494
495 int content_size = static_cast<int>(sizeof(unwinding_info_header) +
496 unwinding_info_header.unwinding_size_);
497 int padding_size = RoundUp(content_size, 8) - content_size;
498 unwinding_info_header.size_ = content_size + padding_size;
499
500 LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
501 sizeof(unwinding_info_header));
502
503 if (code.has_unwinding_info()) {
504 LogWriteBytes(reinterpret_cast<const char*>(code.unwinding_info_start()),
505 code.unwinding_info_size());
506 } else {
507 OFStream perf_output_stream(perf_output_handle_);
508 EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
509 }
510
511 char padding_bytes[] = "\0\0\0\0\0\0\0\0";
512 DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
513 LogWriteBytes(padding_bytes, static_cast<int>(padding_size));
514 }
515
CodeMoveEvent(AbstractCode from,AbstractCode to)516 void PerfJitLogger::CodeMoveEvent(AbstractCode from, AbstractCode to) {
517 // We may receive a CodeMove event if a BytecodeArray object moves. Otherwise
518 // code relocation is not supported.
519 CHECK(from.IsBytecodeArray());
520 }
521
LogWriteBytes(const char * bytes,int size)522 void PerfJitLogger::LogWriteBytes(const char* bytes, int size) {
523 size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
524 DCHECK(static_cast<size_t>(size) == rv);
525 USE(rv);
526 }
527
LogWriteHeader()528 void PerfJitLogger::LogWriteHeader() {
529 DCHECK_NOT_NULL(perf_output_handle_);
530 PerfJitHeader header;
531
532 header.magic_ = PerfJitHeader::kMagic;
533 header.version_ = PerfJitHeader::kVersion;
534 header.size_ = sizeof(header);
535 header.elf_mach_target_ = GetElfMach();
536 header.reserved_ = 0xDEADBEEF;
537 header.process_id_ = process_id_;
538 header.time_stamp_ =
539 static_cast<uint64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis() *
540 base::Time::kMicrosecondsPerMillisecond);
541 header.flags_ = 0;
542
543 LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
544 }
545
546 } // namespace internal
547 } // namespace v8
548
549 #endif // V8_OS_LINUX
550