# define SIDE_EVENT_ENABLED_PRIVATE_MASK 0x00FFFFFFUL
#endif
+#define SIDE_KEY_RESERVED_RANGE_END 0x8
+
+/* Key 0x0 is reserved to match all. */
+#define SIDE_KEY_MATCH_ALL 0x0
/* Key 0x1 is reserved for user event. */
-#define SIDE_USER_EVENT_KEY ((void *)0x1UL)
+#define SIDE_KEY_USER_EVENT 0x1
/* Key 0x2 is reserved for ptrace. */
-#define SIDE_PTRACE_KEY ((void *)0x2UL)
+#define SIDE_KEY_PTRACE 0x2
struct side_events_register_handle {
struct side_list_node node;
void *priv;
};
+struct side_statedump_notification {
+ struct side_list_node node;
+ uint64_t key;
+};
+
struct side_statedump_request_handle {
- struct side_list_node node; /* RCU list. */
+ struct side_list_node node; /* Statedump request RCU list node. */
+ struct side_list_head notification_queue; /* Queue of struct side_statedump_notification */
void (*cb)(void);
+ char *name;
+ enum side_statedump_mode mode;
};
struct side_callback {
void *priv);
} u;
void *priv;
- void *key;
+ uint64_t key;
};
static struct side_rcu_gp_state event_rcu_gp, statedump_rcu_gp;
*/
static pthread_mutex_t side_event_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
static pthread_mutex_t side_statedump_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+static pthread_mutex_t side_key_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Dynamic tracer key allocation. */
+static uint64_t side_key_next = SIDE_KEY_RESERVED_RANGE_END;
static DEFINE_SIDE_LIST_HEAD(side_events_list);
static DEFINE_SIDE_LIST_HEAD(side_tracer_list);
+
+/*
+ * The statedump request list is a RCU list to allow the agent thread to
+ * iterate over this list with a RCU read-side lock.
+ */
static DEFINE_SIDE_LIST_HEAD(side_statedump_list);
/*
* Callback filter key for state dump.
*/
-static __thread void *filter_key;
+static __thread uint64_t filter_key = SIDE_KEY_MATCH_ALL;
/*
* The empty callback has a NULL function callback pointer, which stops
*/
const char side_empty_callback[sizeof(struct side_callback)];
+side_static_event(side_statedump_begin, "side", "statedump_begin",
+ SIDE_LOGLEVEL_INFO, side_field_list(side_field_string("name")));
+side_static_event(side_statedump_end, "side", "statedump_end",
+ SIDE_LOGLEVEL_INFO, side_field_list(side_field_string("name")));
+
/*
* side_ptrace_hook is a place holder for a debugger breakpoint.
* var_struct is NULL if not variadic.
}
static
-void _side_call(const struct side_event_state *event_state, const struct side_arg_vec *side_arg_vec, void *key)
+void _side_call(const struct side_event_state *event_state, const struct side_arg_vec *side_arg_vec, uint64_t key)
{
struct side_rcu_read_state rcu_read_state;
const struct side_event_state_0 *es0;
enabled = __atomic_load_n(&es0->enabled, __ATOMIC_RELAXED);
if (side_unlikely(enabled & SIDE_EVENT_ENABLED_SHARED_MASK)) {
if ((enabled & SIDE_EVENT_ENABLED_SHARED_USER_EVENT_MASK) &&
- (!key || key == SIDE_USER_EVENT_KEY)) {
+ (key == SIDE_KEY_MATCH_ALL || key == SIDE_KEY_USER_EVENT)) {
// TODO: call kernel write.
}
if ((enabled & SIDE_EVENT_ENABLED_SHARED_PTRACE_MASK) &&
- (!key || key == SIDE_PTRACE_KEY))
+ (key == SIDE_KEY_MATCH_ALL || key == SIDE_KEY_PTRACE))
side_ptrace_hook(event_state, side_arg_vec, NULL);
}
side_rcu_read_begin(&event_rcu_gp, &rcu_read_state);
for (side_cb = side_rcu_dereference(es0->callbacks); side_cb->u.call != NULL; side_cb++) {
- /* A NULL key is always a match. */
- if (key && side_cb->key && side_cb->key != key)
+ if (key != SIDE_KEY_MATCH_ALL && side_cb->key != SIDE_KEY_MATCH_ALL && side_cb->key != key)
continue;
side_cb->u.call(es0->desc, side_arg_vec, side_cb->priv);
}
void side_call(const struct side_event_state *event_state, const struct side_arg_vec *side_arg_vec)
{
- _side_call(event_state, side_arg_vec, NULL);
+ _side_call(event_state, side_arg_vec, SIDE_KEY_MATCH_ALL);
}
void side_statedump_call(const struct side_event_state *event_state, const struct side_arg_vec *side_arg_vec)
void _side_call_variadic(const struct side_event_state *event_state,
const struct side_arg_vec *side_arg_vec,
const struct side_arg_dynamic_struct *var_struct,
- void *key)
+ uint64_t key)
{
struct side_rcu_read_state rcu_read_state;
const struct side_event_state_0 *es0;
enabled = __atomic_load_n(&es0->enabled, __ATOMIC_RELAXED);
if (side_unlikely(enabled & SIDE_EVENT_ENABLED_SHARED_MASK)) {
if ((enabled & SIDE_EVENT_ENABLED_SHARED_USER_EVENT_MASK) &&
- (!key || key == SIDE_USER_EVENT_KEY)) {
+ (key == SIDE_KEY_MATCH_ALL || key == SIDE_KEY_USER_EVENT)) {
// TODO: call kernel write.
}
if ((enabled & SIDE_EVENT_ENABLED_SHARED_PTRACE_MASK) &&
- (!key || key == SIDE_PTRACE_KEY))
+ (key == SIDE_KEY_MATCH_ALL || key == SIDE_KEY_PTRACE))
side_ptrace_hook(event_state, side_arg_vec, var_struct);
}
side_rcu_read_begin(&event_rcu_gp, &rcu_read_state);
for (side_cb = side_rcu_dereference(es0->callbacks); side_cb->u.call_variadic != NULL; side_cb++) {
- /* A NULL key is always a match. */
- if (key && side_cb->key && side_cb->key != key)
+ if (key != SIDE_KEY_MATCH_ALL && side_cb->key != SIDE_KEY_MATCH_ALL && side_cb->key != key)
continue;
side_cb->u.call_variadic(es0->desc, side_arg_vec, var_struct, side_cb->priv);
}
const struct side_arg_vec *side_arg_vec,
const struct side_arg_dynamic_struct *var_struct)
{
- _side_call_variadic(event_state, side_arg_vec, var_struct, NULL);
+ _side_call_variadic(event_state, side_arg_vec, var_struct, SIDE_KEY_MATCH_ALL);
}
void side_statedump_call_variadic(const struct side_event_state *event_state,
static
const struct side_callback *side_tracer_callback_lookup(
const struct side_event_description *desc,
- void *call, void *priv, void *key)
+ void *call, void *priv, uint64_t key)
{
struct side_event_state *event_state = side_ptr_get(desc->state);
const struct side_event_state_0 *es0;
static
int _side_tracer_callback_register(struct side_event_description *desc,
- void *call, void *priv, void *key)
+ void *call, void *priv, uint64_t key)
{
struct side_event_state *event_state;
struct side_callback *old_cb, *new_cb;
int side_tracer_callback_register(struct side_event_description *desc,
side_tracer_callback_func call,
- void *priv, void *key)
+ void *priv, uint64_t key)
{
if (desc->flags & SIDE_EVENT_FLAG_VARIADIC)
return SIDE_ERROR_INVAL;
int side_tracer_callback_variadic_register(struct side_event_description *desc,
side_tracer_callback_variadic_func call_variadic,
- void *priv, void *key)
+ void *priv, uint64_t key)
{
if (!(desc->flags & SIDE_EVENT_FLAG_VARIADIC))
return SIDE_ERROR_INVAL;
}
static int _side_tracer_callback_unregister(struct side_event_description *desc,
- void *call, void *priv, void *key)
+ void *call, void *priv, uint64_t key)
{
struct side_event_state *event_state;
struct side_callback *old_cb, *new_cb;
int side_tracer_callback_unregister(struct side_event_description *desc,
side_tracer_callback_func call,
- void *priv, void *key)
+ void *priv, uint64_t key)
{
if (desc->flags & SIDE_EVENT_FLAG_VARIADIC)
return SIDE_ERROR_INVAL;
int side_tracer_callback_variadic_unregister(struct side_event_description *desc,
side_tracer_callback_variadic_func call_variadic,
- void *priv, void *key)
+ void *priv, uint64_t key)
{
if (!(desc->flags & SIDE_EVENT_FLAG_VARIADIC))
return SIDE_ERROR_INVAL;
free(tracer_handle);
}
-struct side_statedump_request_handle *side_statedump_request_notification_register(void (*statedump_cb)(void))
+/* Called with side_statedump_lock held. */
+static
+void queue_statedump_pending(struct side_statedump_request_handle *handle, uint64_t key)
+{
+ struct side_statedump_notification *notif;
+
+ notif = (struct side_statedump_notification *) calloc(1, sizeof(struct side_statedump_notification));
+ if (!notif)
+ abort();
+ notif->key = key;
+ side_list_insert_node_tail(&handle->notification_queue, ¬if->node);
+}
+
+/* Called with side_statedump_lock held. */
+static
+void unqueue_statedump_pending(struct side_statedump_request_handle *handle, uint64_t key)
+{
+ struct side_statedump_notification *notif, *tmp;
+
+ side_list_for_each_entry_safe(notif, tmp, &handle->notification_queue, node) {
+ if (key == SIDE_KEY_MATCH_ALL || key == notif->key) {
+ side_list_remove_node(¬if->node);
+ free(notif);
+ }
+ }
+}
+
+struct side_statedump_request_handle *
+ side_statedump_request_notification_register(const char *state_name,
+ void (*statedump_cb)(void),
+ enum side_statedump_mode mode)
{
struct side_statedump_request_handle *handle;
+ char *name;
if (finalized)
return NULL;
* The statedump request notification should not be registered
* from a notification callback.
*/
- assert(filter_key == NULL);
+ assert(!filter_key);
handle = (struct side_statedump_request_handle *)
calloc(1, sizeof(struct side_statedump_request_handle));
if (!handle)
return NULL;
+ name = strdup(state_name);
+ if (!name)
+ goto name_nomem;
handle->cb = statedump_cb;
+ handle->name = name;
+ handle->mode = mode;
+ side_list_head_init(&handle->notification_queue);
pthread_mutex_lock(&side_statedump_lock);
side_list_insert_node_tail_rcu(&side_statedump_list, &handle->node);
+ /* Queue statedump pending for all tracers. */
+ queue_statedump_pending(handle, SIDE_KEY_MATCH_ALL);
pthread_mutex_unlock(&side_statedump_lock);
- /* Invoke callback for all tracers. */
- statedump_cb();
-
return handle;
+
+name_nomem:
+ free(handle);
+ return NULL;
}
void side_statedump_request_notification_unregister(struct side_statedump_request_handle *handle)
return;
if (!initialized)
side_init();
- assert(filter_key == NULL);
+ assert(!filter_key);
pthread_mutex_lock(&side_statedump_lock);
+ unqueue_statedump_pending(handle, SIDE_KEY_MATCH_ALL);
side_list_remove_node_rcu(&handle->node);
pthread_mutex_unlock(&side_statedump_lock);
side_rcu_wait_grace_period(&statedump_rcu_gp);
+ free(handle->name);
free(handle);
}
-void side_tracer_statedump_request(void *key)
+/* Returns true if the handle has pending statedump requests. */
+bool side_statedump_poll_pending_requests(struct side_statedump_request_handle *handle)
{
- struct side_statedump_request_handle *handle;
- struct side_rcu_read_state rcu_read_state;
+ bool ret;
+
+ if (handle->mode != SIDE_STATEDUMP_MODE_POLLING)
+ return false;
+ pthread_mutex_lock(&side_statedump_lock);
+ ret = !side_list_empty(&handle->notification_queue);
+ pthread_mutex_unlock(&side_statedump_lock);
+ return ret;
+}
+static
+void side_statedump_run(struct side_statedump_request_handle *handle,
+ struct side_statedump_notification *notif)
+{
/* Invoke the state dump callback specifically for the tracer key. */
- filter_key = key;
- side_rcu_read_begin(&statedump_rcu_gp, &rcu_read_state);
- side_list_for_each_entry_rcu(handle, &side_statedump_list, node)
- handle->cb();
- side_rcu_read_end(&statedump_rcu_gp, &rcu_read_state);
- filter_key = NULL;
+ filter_key = notif->key;
+ side_statedump_event_call(side_statedump_begin,
+ side_arg_list(side_arg_string(handle->name)));
+ handle->cb();
+ side_statedump_event_call(side_statedump_end,
+ side_arg_list(side_arg_string(handle->name)));
+ filter_key = SIDE_KEY_MATCH_ALL;
+}
+
+static
+void _side_statedump_run_pending_requests(struct side_statedump_request_handle *handle)
+{
+ struct side_statedump_notification *notif, *tmp;
+ DEFINE_SIDE_LIST_HEAD(tmp_head);
+
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_splice(&handle->notification_queue, &tmp_head);
+ pthread_mutex_lock(&side_statedump_lock);
+
+ /* We are now sole owner of the tmp_head list. */
+ side_list_for_each_entry(notif, &tmp_head, node)
+ side_statedump_run(handle, notif);
+ side_list_for_each_entry_safe(notif, tmp, &tmp_head, node)
+ free(notif);
+}
+
+/*
+ * Only polling mode state dump handles allow application to explicitly handle the
+ * pending requests.
+ */
+int side_statedump_run_pending_requests(struct side_statedump_request_handle *handle)
+{
+ if (handle->mode != SIDE_STATEDUMP_MODE_POLLING)
+ return SIDE_ERROR_INVAL;
+ _side_statedump_run_pending_requests(handle);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Request a state dump for tracer callbacks identified with "key".
+ */
+int side_tracer_statedump_request(uint64_t key)
+{
+ struct side_statedump_request_handle *handle;
+
+ if (key == SIDE_KEY_MATCH_ALL)
+ return SIDE_ERROR_INVAL;
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_for_each_entry(handle, &side_statedump_list, node)
+ queue_statedump_pending(handle, key);
+ pthread_mutex_lock(&side_statedump_lock);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Cancel a statedump request.
+ */
+int side_tracer_statedump_request_cancel(uint64_t key)
+{
+ struct side_statedump_request_handle *handle;
+
+ if (key == SIDE_KEY_MATCH_ALL)
+ return SIDE_ERROR_INVAL;
+ pthread_mutex_lock(&side_statedump_lock);
+ side_list_for_each_entry(handle, &side_statedump_list, node)
+ unqueue_statedump_pending(handle, key);
+ pthread_mutex_lock(&side_statedump_lock);
+ return SIDE_ERROR_OK;
+}
+
+/*
+ * Tracer keys are represented on 64-bit. Return SIDE_ERROR_NOMEM on
+ * overflow (which should never happen in practice).
+ */
+int side_tracer_request_key(uint64_t *key)
+{
+ int ret = SIDE_ERROR_OK;
+
+ pthread_mutex_lock(&side_key_lock);
+ if (side_key_next == 0) {
+ ret = SIDE_ERROR_NOMEM;
+ goto end;
+ }
+ *key = side_key_next++;
+end:
+ pthread_mutex_unlock(&side_key_lock);
+ return ret;
}
void side_init(void)