blob: add96e544ff72bd0ec20d8a79305891ee7105eb9 [file] [log] [blame]
// 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