| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/libplatform/tracing/json-trace-event-listener.h" |
| |
| #include <cmath> |
| |
| #include "base/trace_event/common/trace_event_common.h" |
| #include "perfetto/trace/chrome/chrome_trace_packet.pb.h" |
| #include "perfetto/trace/trace.pb.h" |
| #include "perfetto/tracing.h" |
| #include "src/base/logging.h" |
| #include "src/base/macros.h" |
| |
| namespace v8 { |
| namespace platform { |
| namespace tracing { |
| |
| JSONTraceEventListener::JSONTraceEventListener(std::ostream* stream) |
| : stream_(stream) { |
| *stream_ << "{\"traceEvents\":["; |
| } |
| |
| JSONTraceEventListener::~JSONTraceEventListener() { *stream_ << "]}"; } |
| |
| // TODO(petermarshall): Clean up this code which was copied from trace-writer.cc |
| // once we've removed that file. |
| |
| // Writes the given string, taking care to escape characters when necessary. |
| void JSONTraceEventListener::AppendJSONString(const char* str) { |
| size_t len = strlen(str); |
| *stream_ << "\""; |
| for (size_t i = 0; i < len; ++i) { |
| // All of the permitted escape sequences in JSON strings, as per |
| // https://mathiasbynens.be/notes/javascript-escapes |
| switch (str[i]) { |
| case '\b': |
| *stream_ << "\\b"; |
| break; |
| case '\f': |
| *stream_ << "\\f"; |
| break; |
| case '\n': |
| *stream_ << "\\n"; |
| break; |
| case '\r': |
| *stream_ << "\\r"; |
| break; |
| case '\t': |
| *stream_ << "\\t"; |
| break; |
| case '\"': |
| *stream_ << "\\\""; |
| break; |
| case '\\': |
| *stream_ << "\\\\"; |
| break; |
| // Note that because we use double quotes for JSON strings, |
| // we don't need to escape single quotes. |
| default: |
| *stream_ << str[i]; |
| break; |
| } |
| } |
| *stream_ << "\""; |
| } |
| |
| void JSONTraceEventListener::AppendArgValue( |
| const ::perfetto::protos::ChromeTraceEvent_Arg& arg) { |
| if (arg.has_bool_value()) { |
| *stream_ << (arg.bool_value() ? "true" : "false"); |
| } else if (arg.has_uint_value()) { |
| *stream_ << arg.uint_value(); |
| } else if (arg.has_int_value()) { |
| *stream_ << arg.int_value(); |
| } else if (arg.has_double_value()) { |
| std::string real; |
| double val = arg.double_value(); |
| if (std::isfinite(val)) { |
| std::ostringstream convert_stream; |
| convert_stream << val; |
| real = convert_stream.str(); |
| // Ensure that the number has a .0 if there's no decimal or 'e'. This |
| // makes sure that when we read the JSON back, it's interpreted as a |
| // real rather than an int. |
| if (real.find('.') == std::string::npos && |
| real.find('e') == std::string::npos && |
| real.find('E') == std::string::npos) { |
| real += ".0"; |
| } |
| } else if (std::isnan(val)) { |
| // The JSON spec doesn't allow NaN and Infinity (since these are |
| // objects in EcmaScript). Use strings instead. |
| real = "\"NaN\""; |
| } else if (val < 0) { |
| real = "\"-Infinity\""; |
| } else { |
| real = "\"Infinity\""; |
| } |
| *stream_ << real; |
| } else if (arg.has_string_value()) { |
| AppendJSONString(arg.string_value().c_str()); |
| } else if (arg.has_pointer_value()) { |
| // JSON only supports double and int numbers. |
| // So as not to lose bits from a 64-bit pointer, output as a hex string. |
| *stream_ << "\"0x" << std::hex << arg.pointer_value() << std::dec << "\""; |
| } else if (arg.has_json_value()) { |
| *stream_ << arg.json_value(); |
| } |
| // V8 does not emit proto arguments currently. |
| CHECK(!arg.has_traced_value()); |
| } |
| |
| void JSONTraceEventListener::ProcessPacket( |
| const ::perfetto::protos::TracePacket& packet) { |
| for (const ::perfetto::protos::ChromeTraceEvent& event : |
| packet.chrome_events().trace_events()) { |
| if (append_comma_) *stream_ << ","; |
| append_comma_ = true; |
| |
| // TODO(petermarshall): Handle int64 fields differently? |
| // clang-format off |
| *stream_ << "{\"pid\":" << event.process_id() |
| << ",\"tid\":" << event.thread_id() |
| << ",\"ts\":" << event.timestamp() |
| << ",\"tts\":" << event.thread_timestamp() |
| << ",\"ph\":\"" << static_cast<char>(event.phase()) |
| << "\",\"cat\":\"" << event.category_group_name() |
| << "\",\"name\":\"" << event.name() |
| << "\",\"dur\":" << event.duration() |
| << ",\"tdur\":" << event.thread_duration(); |
| // clang-format on |
| |
| if (event.flags() & |
| (TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT)) { |
| *stream_ << ",\"bind_id\":\"0x" << std::hex << event.bind_id() << "\"" |
| << std::dec; |
| if (event.flags() & TRACE_EVENT_FLAG_FLOW_IN) { |
| *stream_ << ",\"flow_in\":true"; |
| } |
| if (event.flags() & TRACE_EVENT_FLAG_FLOW_OUT) { |
| *stream_ << ",\"flow_out\":true"; |
| } |
| } |
| if (event.flags() & TRACE_EVENT_FLAG_HAS_ID) { |
| if (event.has_scope()) { |
| *stream_ << ",\"scope\":\"" << event.scope() << "\""; |
| } |
| // So as not to lose bits from a 64-bit integer, output as a hex string. |
| *stream_ << ",\"id\":\"0x" << std::hex << event.id() << "\"" << std::dec; |
| } |
| |
| *stream_ << ",\"args\":{"; |
| |
| int i = 0; |
| for (const ::perfetto::protos::ChromeTraceEvent_Arg& arg : event.args()) { |
| if (i++ > 0) *stream_ << ","; |
| *stream_ << "\"" << arg.name() << "\":"; |
| AppendArgValue(arg); |
| } |
| *stream_ << "}}"; |
| } |
| } |
| |
| } // namespace tracing |
| } // namespace platform |
| } // namespace v8 |