| // Copyright 2020 The Chromium OS 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 "sommelier-tracing.h" // NOLINT(build/include_directory) |
| |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <vector> |
| #include <xcb/xproto.h> |
| |
| #include "sommelier.h" // NOLINT(build/include_directory) |
| #include "sommelier-ctx.h" // NOLINT(build/include_directory) |
| |
| #if defined(_M_IA64) || defined(_M_IX86) || defined(__ia64__) || \ |
| defined(__i386__) || defined(__amd64__) || defined(__x86_64__) || \ |
| defined(_M_AMD64) |
| #define HAS_RDTSC |
| #ifdef _MSC_VER |
| #include <intrin.h> |
| #else |
| #include <x86intrin.h> |
| #endif |
| #endif |
| #if defined(_M_ARM) || defined(_M_ARMT) || defined(__arm__) || \ |
| defined(__thumb__) || defined(__aarch64__) |
| // TODO(jbates): support ARM CPU counter |
| #endif |
| |
| #if defined(PERFETTO_TRACING) |
| PERFETTO_TRACK_EVENT_STATIC_STORAGE(); |
| |
| std::unique_ptr<perfetto::TracingSession> tracing_session; |
| |
| void initialize_tracing(bool in_process_backend, bool system_backend) { |
| perfetto::TracingInitArgs args; |
| if (in_process_backend) { |
| args.backends |= perfetto::kInProcessBackend; |
| } |
| if (system_backend) { |
| args.backends |= perfetto::kSystemBackend; |
| } |
| |
| perfetto::Tracing::Initialize(args); |
| perfetto::TrackEvent::Register(); |
| } |
| |
| void enable_tracing(bool create_session) { |
| perfetto::TraceConfig cfg; |
| cfg.add_buffers()->set_size_kb(1024); // Record up to 1 MiB. |
| auto* ds_cfg = cfg.add_data_sources()->mutable_config(); |
| ds_cfg->set_name("track_event"); |
| |
| if (create_session) { |
| tracing_session = perfetto::Tracing::NewTrace(); |
| tracing_session->Setup(cfg); |
| tracing_session->StartBlocking(); |
| } |
| } |
| |
| void dump_trace(const char* trace_filename) { |
| if (!trace_filename || !*trace_filename || !tracing_session) { |
| return; |
| } |
| |
| std::vector<char> trace_data(tracing_session->ReadTraceBlocking()); |
| |
| // Write the trace into a file. |
| int fd = open(trace_filename, O_RDWR | O_CREAT | O_TRUNC, 0644); |
| if (fd == -1) { |
| fprintf(stderr, "error: unable to open trace file %s: %s\n", trace_filename, |
| strerror(errno)); |
| return; |
| } |
| size_t pos = 0; |
| do { |
| ssize_t ret = write(fd, &trace_data[pos], trace_data.size() - pos); |
| if (ret < 0) { |
| if (errno == EINTR || errno == EAGAIN) { |
| continue; |
| } |
| fprintf(stderr, "error: unable to write trace file %s: %s\n", |
| trace_filename, strerror(errno)); |
| close(fd); |
| return; |
| } |
| pos += ret; |
| } while (pos < trace_data.size()); |
| close(fd); |
| } |
| |
| // Returns NULL if atom is not recognized. |
| static const char* xcb_atom_to_string(uint32_t atom) { |
| switch (atom) { |
| case XCB_ATOM_NONE: |
| return "XCB_ATOM_NONE"; |
| case XCB_ATOM_PRIMARY: |
| return "XCB_ATOM_PRIMARY"; |
| case XCB_ATOM_SECONDARY: |
| return "XCB_ATOM_SECONDARY"; |
| case XCB_ATOM_ARC: |
| return "XCB_ATOM_ARC"; |
| case XCB_ATOM_ATOM: |
| return "XCB_ATOM_ATOM"; |
| case XCB_ATOM_BITMAP: |
| return "XCB_ATOM_BITMAP"; |
| case XCB_ATOM_CARDINAL: |
| return "XCB_ATOM_CARDINAL"; |
| case XCB_ATOM_COLORMAP: |
| return "XCB_ATOM_COLORMAP"; |
| case XCB_ATOM_CURSOR: |
| return "XCB_ATOM_CURSOR"; |
| case XCB_ATOM_CUT_BUFFER0: |
| return "XCB_ATOM_CUT_BUFFER0"; |
| case XCB_ATOM_CUT_BUFFER1: |
| return "XCB_ATOM_CUT_BUFFER1"; |
| case XCB_ATOM_CUT_BUFFER2: |
| return "XCB_ATOM_CUT_BUFFER2"; |
| case XCB_ATOM_CUT_BUFFER3: |
| return "XCB_ATOM_CUT_BUFFER3"; |
| case XCB_ATOM_CUT_BUFFER4: |
| return "XCB_ATOM_CUT_BUFFER4"; |
| case XCB_ATOM_CUT_BUFFER5: |
| return "XCB_ATOM_CUT_BUFFER5"; |
| case XCB_ATOM_CUT_BUFFER6: |
| return "XCB_ATOM_CUT_BUFFER6"; |
| case XCB_ATOM_CUT_BUFFER7: |
| return "XCB_ATOM_CUT_BUFFER7"; |
| case XCB_ATOM_DRAWABLE: |
| return "XCB_ATOM_DRAWABLE"; |
| case XCB_ATOM_FONT: |
| return "XCB_ATOM_FONT"; |
| case XCB_ATOM_INTEGER: |
| return "XCB_ATOM_INTEGER"; |
| case XCB_ATOM_PIXMAP: |
| return "XCB_ATOM_PIXMAP"; |
| case XCB_ATOM_POINT: |
| return "XCB_ATOM_POINT"; |
| case XCB_ATOM_RECTANGLE: |
| return "XCB_ATOM_RECTANGLE"; |
| case XCB_ATOM_RESOURCE_MANAGER: |
| return "XCB_ATOM_RESOURCE_MANAGER"; |
| case XCB_ATOM_RGB_COLOR_MAP: |
| return "XCB_ATOM_RGB_COLOR_MAP"; |
| case XCB_ATOM_RGB_BEST_MAP: |
| return "XCB_ATOM_RGB_BEST_MAP"; |
| case XCB_ATOM_RGB_BLUE_MAP: |
| return "XCB_ATOM_RGB_BLUE_MAP"; |
| case XCB_ATOM_RGB_DEFAULT_MAP: |
| return "XCB_ATOM_RGB_DEFAULT_MAP"; |
| case XCB_ATOM_RGB_GRAY_MAP: |
| return "XCB_ATOM_RGB_GRAY_MAP"; |
| case XCB_ATOM_RGB_GREEN_MAP: |
| return "XCB_ATOM_RGB_GREEN_MAP"; |
| case XCB_ATOM_RGB_RED_MAP: |
| return "XCB_ATOM_RGB_RED_MAP"; |
| case XCB_ATOM_STRING: |
| return "XCB_ATOM_STRING"; |
| case XCB_ATOM_VISUALID: |
| return "XCB_ATOM_VISUALID"; |
| case XCB_ATOM_WINDOW: |
| return "XCB_ATOM_WINDOW"; |
| case XCB_ATOM_WM_COMMAND: |
| return "XCB_ATOM_WM_COMMAND"; |
| case XCB_ATOM_WM_HINTS: |
| return "XCB_ATOM_WM_HINTS"; |
| case XCB_ATOM_WM_CLIENT_MACHINE: |
| return "XCB_ATOM_WM_CLIENT_MACHINE"; |
| case XCB_ATOM_WM_ICON_NAME: |
| return "XCB_ATOM_WM_ICON_NAME"; |
| case XCB_ATOM_WM_ICON_SIZE: |
| return "XCB_ATOM_WM_ICON_SIZE"; |
| case XCB_ATOM_WM_NAME: |
| return "XCB_ATOM_WM_NAME"; |
| case XCB_ATOM_WM_NORMAL_HINTS: |
| return "XCB_ATOM_WM_NORMAL_HINTS"; |
| case XCB_ATOM_WM_SIZE_HINTS: |
| return "XCB_ATOM_WM_SIZE_HINTS"; |
| case XCB_ATOM_WM_ZOOM_HINTS: |
| return "XCB_ATOM_WM_ZOOM_HINTS"; |
| case XCB_ATOM_MIN_SPACE: |
| return "XCB_ATOM_MIN_SPACE"; |
| case XCB_ATOM_NORM_SPACE: |
| return "XCB_ATOM_NORM_SPACE"; |
| case XCB_ATOM_MAX_SPACE: |
| return "XCB_ATOM_MAX_SPACE"; |
| case XCB_ATOM_END_SPACE: |
| return "XCB_ATOM_END_SPACE"; |
| case XCB_ATOM_SUPERSCRIPT_X: |
| return "XCB_ATOM_SUPERSCRIPT_X"; |
| case XCB_ATOM_SUPERSCRIPT_Y: |
| return "XCB_ATOM_SUPERSCRIPT_Y"; |
| case XCB_ATOM_SUBSCRIPT_X: |
| return "XCB_ATOM_SUBSCRIPT_X"; |
| case XCB_ATOM_SUBSCRIPT_Y: |
| return "XCB_ATOM_SUBSCRIPT_Y"; |
| case XCB_ATOM_UNDERLINE_POSITION: |
| return "XCB_ATOM_UNDERLINE_POSITION"; |
| case XCB_ATOM_UNDERLINE_THICKNESS: |
| return "XCB_ATOM_UNDERLINE_THICKNESS"; |
| case XCB_ATOM_STRIKEOUT_ASCENT: |
| return "XCB_ATOM_STRIKEOUT_ASCENT"; |
| case XCB_ATOM_STRIKEOUT_DESCENT: |
| return "XCB_ATOM_STRIKEOUT_DESCENT"; |
| case XCB_ATOM_ITALIC_ANGLE: |
| return "XCB_ATOM_ITALIC_ANGLE"; |
| case XCB_ATOM_X_HEIGHT: |
| return "XCB_ATOM_X_HEIGHT"; |
| case XCB_ATOM_QUAD_WIDTH: |
| return "XCB_ATOM_QUAD_WIDTH"; |
| case XCB_ATOM_WEIGHT: |
| return "XCB_ATOM_WEIGHT"; |
| case XCB_ATOM_POINT_SIZE: |
| return "XCB_ATOM_POINT_SIZE"; |
| case XCB_ATOM_RESOLUTION: |
| return "XCB_ATOM_RESOLUTION"; |
| case XCB_ATOM_COPYRIGHT: |
| return "XCB_ATOM_COPYRIGHT"; |
| case XCB_ATOM_NOTICE: |
| return "XCB_ATOM_NOTICE"; |
| case XCB_ATOM_FONT_NAME: |
| return "XCB_ATOM_FONT_NAME"; |
| case XCB_ATOM_FAMILY_NAME: |
| return "XCB_ATOM_FAMILY_NAME"; |
| case XCB_ATOM_FULL_NAME: |
| return "XCB_ATOM_FULL_NAME"; |
| case XCB_ATOM_CAP_HEIGHT: |
| return "XCB_ATOM_CAP_HEIGHT"; |
| case XCB_ATOM_WM_CLASS: |
| return "XCB_ATOM_WM_CLASS"; |
| case XCB_ATOM_WM_TRANSIENT_FOR: |
| return "XCB_ATOM_WM_TRANSIENT_FOR"; |
| default: |
| return NULL; |
| } |
| } |
| |
| // Annotate with the string representation of an atom. |
| // |
| // Supports well-known XCB atoms, and the fetched sl_context::atoms list. (To |
| // add an atom you're interested in debugging, modify |sl_context_atom_name|.) |
| void perfetto_annotate_atom(struct sl_context* ctx, |
| const perfetto::EventContext& perfetto, |
| const char* event_name, |
| xcb_atom_t atom) { |
| auto* dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name(event_name); |
| |
| // Quickest option is to look up the XCB atoms, which have static values. |
| const char* atom_str = xcb_atom_to_string(atom); |
| if (atom_str) { |
| dbg->set_string_value(atom_str, strlen(atom_str)); |
| return; |
| } |
| |
| // Failing that, check if we've fetched this atom. |
| for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { |
| if (atom == ctx->atoms[i].value) { |
| const char* name = sl_context_atom_name(i); |
| if (name != nullptr) { |
| dbg->set_string_value(name, strlen(name)); |
| return; |
| } |
| } |
| } |
| |
| // If we reach here, we didn't find the atom name. |
| // We could ask the X server but that would require a round-trip. |
| std::string unknown("<unknown atom #"); |
| unknown += std::to_string(atom); |
| unknown += '>'; |
| dbg->set_string_value(unknown); |
| } |
| |
| void perfetto_annotate_xcb_property_state(const perfetto::EventContext& event, |
| const char* name, |
| unsigned int state) { |
| auto* dbg = event.event()->add_debug_annotations(); |
| dbg->set_name(name); |
| if (state == XCB_PROPERTY_NEW_VALUE) { |
| static const std::string prop_new("XCB_PROPERTY_NEW_VALUE"); |
| dbg->set_string_value(prop_new); |
| } else if (state == XCB_PROPERTY_DELETE) { |
| static const std::string prop_delete("XCB_PROPERTY_DELETE"); |
| dbg->set_string_value(prop_delete); |
| } else { |
| static const std::string unknown("<unknown>"); |
| dbg->set_string_value(unknown); |
| } |
| } |
| |
| // Annotate the given Perfetto EventContext with the name (if known) and the ID |
| // of the given window. |
| // |
| // Slow (iterates a linked list); only intended to be called if tracing is |
| // enabled. |
| void perfetto_annotate_window(struct sl_context* ctx, |
| const perfetto::EventContext& perfetto, |
| const char* event_name, |
| xcb_window_t window_id) { |
| auto* dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name(event_name); |
| struct sl_window* window = sl_lookup_window(ctx, window_id); |
| std::ostringstream value; |
| if (window != NULL && window->name != NULL) { |
| value << window->name << " <window #"; |
| } else { |
| value << "<unknown window #"; |
| } |
| // Always append the ID so we can track windows across their lifecycle. |
| value << window_id << '>'; |
| dbg->set_string_value(value.str()); |
| } |
| |
| void perfetto_annotate_size_hints(const perfetto::EventContext& perfetto, |
| const sl_wm_size_hints& size_hints) { |
| auto* dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.flags"); |
| std::string flags; |
| if (size_hints.flags & US_POSITION) |
| flags += "US_POSITION|"; |
| if (size_hints.flags & US_SIZE) |
| flags += "US_SIZE|"; |
| if (size_hints.flags & P_POSITION) |
| flags += "P_POSITION|"; |
| if (size_hints.flags & P_SIZE) |
| flags += "P_SIZE|"; |
| if (size_hints.flags & P_MIN_SIZE) |
| flags += "P_MIN_SIZE|"; |
| if (size_hints.flags & P_MAX_SIZE) |
| flags += "P_MAX_SIZE|"; |
| if (size_hints.flags & P_RESIZE_INC) |
| flags += "P_RESIZE_INC|"; |
| if (size_hints.flags & P_ASPECT) |
| flags += "P_ASPECT|"; |
| if (size_hints.flags & P_BASE_SIZE) |
| flags += "P_BASE_SIZE|"; |
| if (size_hints.flags & P_WIN_GRAVITY) |
| flags += "P_WIN_GRAVITY|"; |
| if (!flags.empty()) |
| flags.pop_back(); // remove trailing '|' |
| dbg->set_string_value(flags); |
| |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.x"); |
| dbg->set_int_value(size_hints.x); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.y"); |
| dbg->set_int_value(size_hints.y); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.width"); |
| dbg->set_int_value(size_hints.width); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.height"); |
| dbg->set_int_value(size_hints.height); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.min_width"); |
| dbg->set_int_value(size_hints.min_width); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.min_height"); |
| dbg->set_int_value(size_hints.min_height); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.max_width"); |
| dbg->set_int_value(size_hints.max_width); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.max_height"); |
| dbg->set_int_value(size_hints.max_height); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.width_inc"); |
| dbg->set_int_value(size_hints.width_inc); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.height_inc"); |
| dbg->set_int_value(size_hints.height_inc); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.min_aspect.x"); |
| dbg->set_int_value(size_hints.min_aspect.x); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.min_aspect.y"); |
| dbg->set_int_value(size_hints.min_aspect.y); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.max_aspect.x"); |
| dbg->set_int_value(size_hints.max_aspect.x); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.max_aspect.y"); |
| dbg->set_int_value(size_hints.max_aspect.y); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.base_width"); |
| dbg->set_int_value(size_hints.base_width); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.base_height"); |
| dbg->set_int_value(size_hints.base_height); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("size_hints.win_gravity"); |
| dbg->set_int_value(size_hints.win_gravity); |
| } |
| |
| // Add a Perfetto annotation for an X property storing a list of cardinals. |
| void perfetto_annotate_cardinal_list(const perfetto::EventContext& perfetto, |
| const char* event_name, |
| xcb_get_property_reply_t* reply) { |
| auto* dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name(event_name); |
| |
| if (reply == nullptr) { |
| static const std::string null_str("<null>"); |
| dbg->set_string_value(null_str); |
| return; |
| } |
| |
| uint32_t length = xcb_get_property_value_length(reply); |
| if (length % sizeof(uint32_t) != 0) { |
| static const std::string invalid("<invalid>"); |
| dbg->set_string_value(invalid); |
| return; |
| } |
| |
| uint32_t* val = static_cast<uint32_t*>(xcb_get_property_value(reply)); |
| uint32_t items = length / sizeof(uint32_t); |
| if (items == 0) { |
| static const std::string empty("<empty>"); |
| dbg->set_string_value(empty); |
| return; |
| } |
| |
| std::ostringstream str; |
| str << '[' << val[0]; |
| for (uint32_t i = 1; i < items; ++i) { |
| str << ", " << val[i]; |
| } |
| str << ']'; |
| dbg->set_string_value(str.str()); |
| } |
| |
| static inline uint64_t get_cpu_ticks() { |
| #ifdef HAS_RDTSC |
| return __rdtsc(); |
| #else |
| return 0; |
| #endif |
| } |
| |
| static inline uint64_t get_timestamp_ns(clockid_t cid) { |
| struct timespec ts = {}; |
| clock_gettime(cid, &ts); |
| return static_cast<uint64_t>(ts.tv_sec * 1000000000LL + ts.tv_nsec); |
| } |
| |
| void perfetto_annotate_time_sync(const perfetto::EventContext& perfetto) { |
| uint64_t boot_time = get_timestamp_ns(CLOCK_BOOTTIME); |
| uint64_t cpu_time = get_cpu_ticks(); |
| uint64_t monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC); |
| // Read again to avoid cache miss overhead. |
| boot_time = get_timestamp_ns(CLOCK_BOOTTIME); |
| cpu_time = get_cpu_ticks(); |
| monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC); |
| |
| auto* dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("clock_sync_boottime"); |
| dbg->set_uint_value(boot_time); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("clock_sync_monotonic"); |
| dbg->set_uint_value(monotonic_time); |
| dbg = perfetto.event()->add_debug_annotations(); |
| dbg->set_name("clock_sync_cputime"); |
| dbg->set_uint_value(cpu_time); |
| } |
| |
| #else |
| |
| // Stubs. |
| |
| void initialize_tracing(bool in_process_backend, bool system_backend) {} |
| |
| void enable_tracing(bool create_session) {} |
| |
| void dump_trace(const char* trace_filename) {} |
| |
| #endif // PERFETTO_TRACING |