Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 140 additions & 21 deletions include/coz.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
#if !defined(COZ_H)
#define COZ_H

/// Embedders can define COZ_ENABLED explicitly to 1 or 0 to globally to
/// enable/disable coz tracepoints at build time without needing to modify code.
/// If set to 0 before including this header for the first time, all macros
/// become noops. This is useful to commit coz tracepoints into a codebase but
/// not require linking against the coz runtime and not having any code bloat
/// nor runtime overhead from using the macros.
#ifndef COZ_ENABLED
#define COZ_ENABLED 1
#endif

#if COZ_ENABLED

#ifndef __USE_GNU
# define __USE_GNU
#endif
Expand Down Expand Up @@ -37,31 +49,57 @@ typedef struct {
size_t backoff; // Used to batch updates to the shared counter. Currently unused.
} coz_counter_t;

// The type of the _coz_get_counter function
typedef coz_counter_t* (*coz_get_counter_t)(int, const char*);

// Locate and invoke _coz_get_counter
static coz_counter_t* _call_coz_get_counter(int type, const char* name) {
static unsigned char _initialized = 0;
static coz_get_counter_t fn; // The pointer to _coz_get_counter

if(!_initialized) {
if(dlsym) {
// Locate the _coz_get_counter method
void* p = dlsym(RTLD_DEFAULT, "_coz_get_counter");

// Use memcpy to avoid pedantic GCC complaint about storing function pointer in void*
memcpy(&fn, &p, sizeof(p));
}

_initialized = 1;
// Use memcpy to avoid pedantic GCC complaint about storing function pointer in void*
static unsigned char initialized = dlsym == NULL;
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization logic is inverted. When dlsym is NULL, initialized is set to true, which means the function will never attempt to look up the symbol. This should be 'initialized = false' or 'initialized = 0' to properly initialize and allow symbol lookup when dlsym is available.

Copilot uses AI. Check for mistakes.
static coz_counter_t* (*fn)(int, const char*);

if (!initialized) {
void* p = dlsym(RTLD_DEFAULT, "_coz_get_counter");
memcpy(&fn, &p, sizeof(p));
initialized = true;
}

// Call the function, or return null if profiler is not found
if(fn) return fn(type, name);

if (fn) return fn(type, name);
else return 0;
}

static void _call_coz_pre_block() {
static unsigned char initialized = dlsym == 0;
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization logic is inverted. When dlsym is 0 (NULL), initialized is set to true, which means the function will never attempt to look up the symbol. This should be 'initialized = false' or 'initialized = 0' to properly initialize and allow symbol lookup when dlsym is available.

Copilot uses AI. Check for mistakes.
static void (*fn)();

if (!initialized) {
void* p = dlsym(RTLD_DEFAULT, "_coz_pre_block");
memcpy(&fn, &p, sizeof(p));
initialized = true;
}
if (fn) return fn();
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement is unnecessary for a void function. While not a bug, it's inconsistent with typical C/C++ style and should be changed to just call the function without the return keyword.

Copilot uses AI. Check for mistakes.
}

static void _call_coz_post_block(bool skip_delays) {
static unsigned char initialized = dlsym == 0;
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization logic is inverted. When dlsym is 0 (NULL), initialized is set to true, which means the function will never attempt to look up the symbol. This should be 'initialized = false' or 'initialized = 0' to properly initialize and allow symbol lookup when dlsym is available.

Copilot uses AI. Check for mistakes.
static void (*fn)(bool);

if (!initialized) {
void* p = dlsym(RTLD_DEFAULT, "_coz_post_block");
memcpy(&fn, &p, sizeof(p));
initialized = true;
}
if (fn) return fn(skip_delays);
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement is unnecessary for a void function. While not a bug, it's inconsistent with typical C/C++ style and should be changed to just call the function without the return keyword.

Copilot uses AI. Check for mistakes.
}

static void _call_coz_wake_other() {
static unsigned char initialized = dlsym == 0;
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization logic is inverted. When dlsym is 0 (NULL), initialized is set to true, which means the function will never attempt to look up the symbol. This should be 'initialized = false' or 'initialized = 0' to properly initialize and allow symbol lookup when dlsym is available.

Copilot uses AI. Check for mistakes.
static void (*fn)();

if (!initialized) {
void* p = dlsym(RTLD_DEFAULT, "_coz_wake_other");
memcpy(&fn, &p, sizeof(p));
initialized = true;
}
if (fn) return fn();
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return statement is unnecessary for a void function. While not a bug, it's inconsistent with typical C/C++ style and should be changed to just call the function without the return keyword.

Copilot uses AI. Check for mistakes.
}

// Macro to initialize and increment a counter
#define COZ_INCREMENT_COUNTER(type, name) \
if(1) { \
Expand All @@ -77,17 +115,98 @@ static coz_counter_t* _call_coz_get_counter(int type, const char* name) {
} \
}

#define STR2(x) #x
#define STR2(x) #x
#define STR(x) STR2(x)

/// Indicate progress for the counter with the given name.
///
/// Example:
/// ```
/// while (more_events()) {
/// COZ_PROGRESS_NAMED("event_processed");
/// process_event();
/// }
/// ```
#define COZ_PROGRESS_NAMED(name) COZ_INCREMENT_COUNTER(COZ_COUNTER_TYPE_THROUGHPUT, name)

/// Indicate progress for the counter named implicitly after the file and line
/// number it is placed on.
///
/// Example:
/// ```
/// while (more_events()) {
/// COZ_PROGRESS;
/// process_event();
/// }
/// ```
#define COZ_PROGRESS COZ_INCREMENT_COUNTER(COZ_COUNTER_TYPE_THROUGHPUT, __FILE__ ":" STR(__LINE__))
#define COZ_BEGIN(name) COZ_INCREMENT_COUNTER(COZ_COUNTER_TYPE_BEGIN, name)
#define COZ_END(name) COZ_INCREMENT_COUNTER(COZ_COUNTER_TYPE_END, name)

/// Call before (possibly) blocking (e.g. if you're about to maybe block on a
/// futex).
///
/// Example:
/// ```
/// lock() {
/// int expected = 0;
/// if (!std::atomic_compare_exchange_weak(&futex, &expected, 1)) {
/// COZ_PRE_BLOCK();
/// syscall(SYS_futex, &futex, FUTEX_WAIT, ...);
/// COZ_POST_BLOCK();
/// }
/// }
/// ```
#define COZ_PRE_BLOCK() _call_coz_pre_block()

/// Call after unblocking. If skip_delays is true, all delays inserted during
/// the blocked period will be skipped.
///
/// Example:
/// ```
/// lock() {
/// int expected = 0;
/// if (!std::atomic_compare_exchange_weak(&futex, &expected, 1)) {
/// COZ_PRE_BLOCK();
/// syscall(SYS_futex, &futex, FUTEX_WAIT, ...);
/// COZ_POST_BLOCK();
/// }
/// }
/// ```
#define COZ_POST_BLOCK(skip_delays) _call_coz_post_block(skip_delays)

/// Ensure a thread has executed all the required delays before possibly
/// unblocking another thread.
///
/// Example:
/// ```
/// // Unlocking the futex.
/// unlock() {
/// let have_waiters = ...;
/// std::atomic_store(&futex, 0);
/// if (have_waiters) {
/// COZ_WAKE_OTHER();
/// syscall(SYS_FUTEX, &futex, FUTEX_WAKE, ...);
/// }
/// }
/// ```
#define COZ_WAKE_OTHER() _call_coz_wake_other()

#if defined(__cplusplus)
}
#endif

#else

#define _COZ_NOOP() do {} while(0)
#define COZ_PROGRESS_NAMED(name) _COZ_NOOP()
#define COZ_PROGRESS() _COZ_NOOP()
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macro name COZ_PROGRESS() should not include parentheses. It should be defined as COZ_PROGRESS to match the actual macro name and usage in the enabled section (line 142). The parentheses make it a function-like macro invocation rather than a definition.

Suggested change
#define COZ_PROGRESS() _COZ_NOOP()
#define COZ_PROGRESS _COZ_NOOP()

Copilot uses AI. Check for mistakes.
#define COZ_BEGIN(name) _COZ_NOOP()
#define COZ_END(name) _COZ_NOOP()
#define COZ_PRE_BLOCK() _COZ_NOOP()
#define COZ_POST_BLOCK(skip_delay) _COZ_NOOP()
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter name inconsistency: The macro is defined with parameter name 'skip_delay' (singular), but everywhere else in the codebase it's called 'skip_delays' (plural). This should be 'skip_delays' to maintain consistency with the function signatures in libcoz.cpp and the documentation.

Suggested change
#define COZ_POST_BLOCK(skip_delay) _COZ_NOOP()
#define COZ_POST_BLOCK(skip_delays) _COZ_NOOP()

Copilot uses AI. Check for mistakes.
#define COZ_WAKE_OTHER() _COZ_NOOP()

#endif

#endif
18 changes: 15 additions & 3 deletions libcoz/libcoz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,35 @@ extern "C" coz_counter_t* _coz_get_counter(progress_point_type t, const char* na
throughput_point* p = profiler::get_instance().get_throughput_point(name);
if(p) return p->get_counter_struct();
else return nullptr;

} else if(t == progress_point_type::begin) {
latency_point* p = profiler::get_instance().get_latency_point(name);
if(p) return p->get_begin_counter_struct();
else return nullptr;

} else if(t == progress_point_type::end) {
latency_point* p = profiler::get_instance().get_latency_point(name);
if(p) return p->get_end_counter_struct();
else return nullptr;

} else {
WARNING << "Unknown progress point type " << ((int)t) << " named " << name;
return nullptr;
}
}

extern "C" void _coz_pre_block() {
profiler::get_instance().pre_block();
}

extern "C" void _coz_post_block(bool skip_delays) {
profiler::get_instance().post_block(skip_delays);
}

extern "C" void _coz_wake_other() {
profiler::get_instance().catch_up();
}

/**
* Read a link's contents and return it as a string
*/
Expand Down
3 changes: 2 additions & 1 deletion libcoz/profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ class profiler {
state->pre_block_time = _global_delay.load();
}

/// Call after unblocking. If by_thread is true, delays will be skipped
/// Call after unblocking. If skip_delays is true, all delays inserted
/// during the blocked period will be skipped.
void post_block(bool skip_delays) {
thread_state* state = get_thread_state();
if(!state)
Expand Down