X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Fbin%2Flttng-sessiond%2Fnotification-thread-events.c;h=98757737dbfd2d13357d62f0c2a20a5b45683163;hp=66223e9d340e89f8c39b8dc3b5b5bfbecbbf096a;hb=9b63a4aa352c184984395c097f86d5e54c295012;hpb=1fa1cfec753071454ca0eeb1adb426d60a606dcc diff --git a/src/bin/lttng-sessiond/notification-thread-events.c b/src/bin/lttng-sessiond/notification-thread-events.c index 66223e9d3..98757737d 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ b/src/bin/lttng-sessiond/notification-thread-events.c @@ -19,9 +19,6 @@ #include #include -#include "notification-thread.h" -#include "notification-thread-events.h" -#include "notification-thread-commands.h" #include #include #include @@ -31,17 +28,25 @@ #include #include #include -#include +#include #include #include #include +#include #include + #include #include #include #include #include +#include "notification-thread.h" +#include "notification-thread-events.h" +#include "notification-thread-commands.h" +#include "lttng-sessiond.h" +#include "kernel.h" + #define CLIENT_POLL_MASK_IN (LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP) #define CLIENT_POLL_MASK_IN_OUT (CLIENT_POLL_MASK_IN | LPOLLOUT) @@ -84,7 +89,7 @@ struct notification_client { uid_t uid; gid_t gid; /* - * Indicates if the credentials and versions of the client has been + * Indicates if the credentials and versions of the client have been * checked. */ bool validated; @@ -97,6 +102,11 @@ struct notification_client { struct cds_lfht_node client_socket_ht_node; struct { struct { + /* + * During the reception of a message, the reception + * buffers' "size" is set to contain the current + * message's complete payload. + */ struct lttng_dynamic_buffer buffer; /* Bytes left to receive for the current message. */ size_t bytes_to_receive; @@ -106,12 +116,13 @@ struct notification_client { * Indicates whether or not credentials are expected * from the client. */ - bool receive_creds; + bool expect_creds; /* * Indicates whether or not credentials were received * from the client. */ bool creds_received; + /* Only used during credentials reception. */ lttng_sock_cred creds; } inbound; struct { @@ -142,8 +153,42 @@ struct channel_state_sample { struct cds_lfht_node channel_state_ht_node; uint64_t highest_usage; uint64_t lowest_usage; + uint64_t channel_total_consumed; }; +static unsigned long hash_channel_key(struct channel_key *key); +static int evaluate_condition(const struct lttng_condition *condition, + struct lttng_evaluation **evaluation, + const struct notification_thread_state *state, + const struct channel_state_sample *previous_sample, + const struct channel_state_sample *latest_sample, + uint64_t previous_session_consumed_total, + uint64_t latest_session_consumed_total, + struct channel_info *channel_info); +static +int send_evaluation_to_clients(const struct lttng_trigger *trigger, + const struct lttng_evaluation *evaluation, + struct notification_client_list *client_list, + struct notification_thread_state *state, + uid_t channel_uid, gid_t channel_gid); + + +static +void session_info_destroy(void *_data); +static +void session_info_get(struct session_info *session_info); +static +void session_info_put(struct session_info *session_info); +static +struct session_info *session_info_create(const char *name, + uid_t uid, gid_t gid); +static +void session_info_add_channel(struct session_info *session_info, + struct channel_info *channel_info); +static +void session_info_remove_channel(struct session_info *session_info, + struct channel_info *channel_info); + static int match_client(struct cds_lfht_node *node, const void *key) { @@ -247,7 +292,7 @@ int match_client_list_condition(struct cds_lfht_node *node, const void *key) static unsigned long lttng_condition_buffer_usage_hash( - struct lttng_condition *_condition) + const struct lttng_condition *_condition) { unsigned long hash = 0; struct lttng_condition_buffer_usage *condition; @@ -271,7 +316,7 @@ unsigned long lttng_condition_buffer_usage_hash( val = condition->threshold_ratio.value * (double) UINT32_MAX; hash ^= hash_key_u64(&val, lttng_ht_seed); - } else if (condition->threshold_ratio.set) { + } else if (condition->threshold_bytes.set) { uint64_t val; val = condition->threshold_bytes.value; @@ -280,24 +325,55 @@ unsigned long lttng_condition_buffer_usage_hash( return hash; } +static +unsigned long lttng_condition_session_consumed_size_hash( + const struct lttng_condition *_condition) +{ + unsigned long hash = 0; + struct lttng_condition_session_consumed_size *condition; + uint64_t val; + + condition = container_of(_condition, + struct lttng_condition_session_consumed_size, parent); + + if (condition->session_name) { + hash ^= hash_key_str(condition->session_name, lttng_ht_seed); + } + val = condition->consumed_threshold_bytes.value; + hash ^= hash_key_u64(&val, lttng_ht_seed); + return hash; +} + /* * The lttng_condition hashing code is kept in this file (rather than * condition.c) since it makes use of GPLv2 code (hashtable utils), which we * don't want to link in liblttng-ctl. */ static -unsigned long lttng_condition_hash(struct lttng_condition *condition) +unsigned long lttng_condition_hash(const struct lttng_condition *condition) { switch (condition->type) { case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: return lttng_condition_buffer_usage_hash(condition); + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + return lttng_condition_session_consumed_size_hash(condition); default: ERR("[notification-thread] Unexpected condition type caught"); abort(); } } +static +unsigned long hash_channel_key(struct channel_key *key) +{ + unsigned long key_hash = hash_key_u64(&key->key, lttng_ht_seed); + unsigned long domain_hash = hash_key_ulong( + (void *) (unsigned long) key->domain, lttng_ht_seed); + + return key_hash ^ domain_hash; +} + static void channel_info_destroy(struct channel_info *channel_info) { @@ -305,48 +381,266 @@ void channel_info_destroy(struct channel_info *channel_info) return; } - if (channel_info->session_name) { - free(channel_info->session_name); + if (channel_info->session_info) { + session_info_remove_channel(channel_info->session_info, + channel_info); + session_info_put(channel_info->session_info); } - if (channel_info->channel_name) { - free(channel_info->channel_name); + if (channel_info->name) { + free(channel_info->name); } free(channel_info); } +/* Don't call directly, use the ref-counting mechanism. */ static -struct channel_info *channel_info_copy(struct channel_info *channel_info) +void session_info_destroy(void *_data) { - struct channel_info *copy = zmalloc(sizeof(*channel_info)); + struct session_info *session_info = _data; + int ret; - assert(channel_info); - assert(channel_info->session_name); - assert(channel_info->channel_name); + assert(session_info); + if (session_info->channel_infos_ht) { + ret = cds_lfht_destroy(session_info->channel_infos_ht, NULL); + if (ret) { + ERR("[notification-thread] Failed to destroy channel information hash table"); + } + } + free(session_info->name); + free(session_info); +} - if (!copy) { - goto end; +static +void session_info_get(struct session_info *session_info) +{ + if (!session_info) { + return; } + lttng_ref_get(&session_info->ref); +} - memcpy(copy, channel_info, sizeof(*channel_info)); - copy->session_name = NULL; - copy->channel_name = NULL; +static +void session_info_put(struct session_info *session_info) +{ + if (!session_info) { + return; + } + lttng_ref_put(&session_info->ref); +} + +static +struct session_info *session_info_create(const char *name, uid_t uid, gid_t gid) +{ + struct session_info *session_info; + + assert(name); + + session_info = zmalloc(sizeof(*session_info)); + if (!session_info) { + goto end; + } + lttng_ref_init(&session_info->ref, session_info_destroy); - copy->session_name = strdup(channel_info->session_name); - if (!copy->session_name) { + session_info->channel_infos_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!session_info->channel_infos_ht) { goto error; } - copy->channel_name = strdup(channel_info->channel_name); - if (!copy->channel_name) { + + cds_lfht_node_init(&session_info->sessions_ht_node); + session_info->name = strdup(name); + if (!session_info->name) { goto error; } + session_info->uid = uid; + session_info->gid = gid; +end: + return session_info; +error: + session_info_put(session_info); + return NULL; +} + +static +void session_info_add_channel(struct session_info *session_info, + struct channel_info *channel_info) +{ + rcu_read_lock(); + cds_lfht_add(session_info->channel_infos_ht, + hash_channel_key(&channel_info->key), + &channel_info->session_info_channels_ht_node); + rcu_read_unlock(); +} + +static +void session_info_remove_channel(struct session_info *session_info, + struct channel_info *channel_info) +{ + rcu_read_lock(); + cds_lfht_del(session_info->channel_infos_ht, + &channel_info->session_info_channels_ht_node); + rcu_read_unlock(); +} + +static +struct channel_info *channel_info_create(const char *channel_name, + struct channel_key *channel_key, uint64_t channel_capacity, + struct session_info *session_info) +{ + struct channel_info *channel_info = zmalloc(sizeof(*channel_info)); + + if (!channel_info) { + goto end; + } + cds_lfht_node_init(&channel_info->channels_ht_node); + cds_lfht_node_init(&channel_info->session_info_channels_ht_node); + memcpy(&channel_info->key, channel_key, sizeof(*channel_key)); + channel_info->capacity = channel_capacity; + + channel_info->name = strdup(channel_name); + if (!channel_info->name) { + goto error; + } + + /* + * Set the references between session and channel infos: + * - channel_info holds a strong reference to session_info + * - session_info holds a weak reference to channel_info + */ + session_info_get(session_info); + session_info_add_channel(session_info, channel_info); + channel_info->session_info = session_info; end: - return copy; + return channel_info; error: - channel_info_destroy(copy); + channel_info_destroy(channel_info); return NULL; } +/* This function must be called with the RCU read lock held. */ +static +int evaluate_condition_for_client(struct lttng_trigger *trigger, + struct lttng_condition *condition, + struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + struct channel_info *channel_info = NULL; + struct channel_key *channel_key = NULL; + struct channel_state_sample *last_sample = NULL; + struct lttng_channel_trigger_list *channel_trigger_list = NULL; + struct lttng_evaluation *evaluation = NULL; + struct notification_client_list client_list = { 0 }; + struct notification_client_list_element client_list_element = { 0 }; + + assert(trigger); + assert(condition); + assert(client); + assert(state); + + /* Find the channel associated with the trigger. */ + cds_lfht_for_each_entry(state->channel_triggers_ht, &iter, + channel_trigger_list , channel_triggers_ht_node) { + struct lttng_trigger_list_element *element; + + cds_list_for_each_entry(element, &channel_trigger_list->list, node) { + const struct lttng_condition *current_condition = + lttng_trigger_get_const_condition( + element->trigger); + + assert(current_condition); + if (!lttng_condition_is_equal(condition, + current_condition)) { + continue; + } + + /* Found the trigger, save the channel key. */ + channel_key = &channel_trigger_list->channel_key; + break; + } + if (channel_key) { + /* The channel key was found stop iteration. */ + break; + } + } + + if (!channel_key){ + /* No channel found; normal exit. */ + DBG("[notification-thread] No channel associated with newly subscribed-to condition"); + ret = 0; + goto end; + } + + /* Fetch channel info for the matching channel. */ + cds_lfht_lookup(state->channels_ht, + hash_channel_key(channel_key), + match_channel_info, + channel_key, + &iter); + node = cds_lfht_iter_get_node(&iter); + assert(node); + channel_info = caa_container_of(node, struct channel_info, + channels_ht_node); + + /* Retrieve the channel's last sample, if it exists. */ + cds_lfht_lookup(state->channel_state_ht, + hash_channel_key(channel_key), + match_channel_state_sample, + channel_key, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + last_sample = caa_container_of(node, + struct channel_state_sample, + channel_state_ht_node); + } else { + /* Nothing to evaluate, no sample was ever taken. Normal exit */ + DBG("[notification-thread] No channel sample associated with newly subscribed-to condition"); + ret = 0; + goto end; + } + + ret = evaluate_condition(condition, &evaluation, state, + NULL, last_sample, + 0, channel_info->session_info->consumed_data_size, + channel_info); + if (ret) { + WARN("[notification-thread] Fatal error occurred while evaluating a newly subscribed-to condition"); + goto end; + } + + if (!evaluation) { + /* Evaluation yielded nothing. Normal exit. */ + DBG("[notification-thread] Newly subscribed-to condition evaluated to false, nothing to report to client"); + ret = 0; + goto end; + } + + /* + * Create a temporary client list with the client currently + * subscribing. + */ + cds_lfht_node_init(&client_list.notification_trigger_ht_node); + CDS_INIT_LIST_HEAD(&client_list.list); + client_list.trigger = trigger; + + CDS_INIT_LIST_HEAD(&client_list_element.node); + client_list_element.client = client; + cds_list_add(&client_list_element.node, &client_list.list); + + /* Send evaluation result to the newly-subscribed client. */ + DBG("[notification-thread] Newly subscribed-to condition evaluated to true, notifying client"); + ret = send_evaluation_to_clients(trigger, evaluation, &client_list, + state, channel_info->session_info->uid, + channel_info->session_info->gid); + +end: + return ret; +} + static int notification_thread_client_subscribe(struct notification_client *client, struct lttng_condition *condition, @@ -394,11 +688,6 @@ int notification_thread_client_subscribe(struct notification_client *client, condition_list_element->condition = condition; cds_list_add(&condition_list_element->node, &client->condition_list); - /* - * Add the client to the list of clients interested in a given trigger - * if a "notification" trigger with a corresponding condition was - * added prior. - */ cds_lfht_lookup(state->notification_trigger_clients_ht, lttng_condition_hash(condition), match_client_list_condition, @@ -406,12 +695,35 @@ int notification_thread_client_subscribe(struct notification_client *client, &iter); node = cds_lfht_iter_get_node(&iter); if (!node) { + /* + * No notification-emiting trigger registered with this + * condition. We don't evaluate the condition right away + * since this trigger is not registered yet. + */ free(client_list_element); goto end_unlock; } client_list = caa_container_of(node, struct notification_client_list, notification_trigger_ht_node); + /* + * The condition to which the client just subscribed is evaluated + * at this point so that conditions that are already TRUE result + * in a notification being sent out. + */ + if (evaluate_condition_for_client(client_list->trigger, condition, + client, state)) { + WARN("[notification-thread] Evaluation of a condition on client subscription failed, aborting."); + ret = -1; + free(client_list_element); + goto end_unlock; + } + + /* + * Add the client to the list of clients interested in a given trigger + * if a "notification" trigger with a corresponding condition was + * added prior. + */ client_list_element->client = client; CDS_INIT_LIST_HEAD(&client_list_element->node); cds_list_add(&client_list_element->node, &client_list->list); @@ -564,51 +876,90 @@ end: } static -bool trigger_applies_to_channel(struct lttng_trigger *trigger, - struct channel_info *info) +bool buffer_usage_condition_applies_to_channel( + struct lttng_condition *condition, + struct channel_info *channel_info) { enum lttng_condition_status status; - struct lttng_condition *condition; - const char *trigger_session_name = NULL; - const char *trigger_channel_name = NULL; - enum lttng_domain_type trigger_domain; + enum lttng_domain_type condition_domain; + const char *condition_session_name = NULL; + const char *condition_channel_name = NULL; - condition = lttng_trigger_get_condition(trigger); - if (!condition) { + status = lttng_condition_buffer_usage_get_domain_type(condition, + &condition_domain); + assert(status == LTTNG_CONDITION_STATUS_OK); + if (channel_info->key.domain != condition_domain) { goto fail; } - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - break; - default: + status = lttng_condition_buffer_usage_get_session_name( + condition, &condition_session_name); + assert((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); + + status = lttng_condition_buffer_usage_get_channel_name( + condition, &condition_channel_name); + assert((status == LTTNG_CONDITION_STATUS_OK) && condition_channel_name); + + if (strcmp(channel_info->session_info->name, condition_session_name)) { + goto fail; + } + if (strcmp(channel_info->name, condition_channel_name)) { goto fail; } - status = lttng_condition_buffer_usage_get_domain_type(condition, - &trigger_domain); - assert(status == LTTNG_CONDITION_STATUS_OK); - if (info->key.domain != trigger_domain) { + return true; +fail: + return false; +} + +static +bool session_consumed_size_condition_applies_to_channel( + struct lttng_condition *condition, + struct channel_info *channel_info) +{ + enum lttng_condition_status status; + const char *condition_session_name = NULL; + + status = lttng_condition_session_consumed_size_get_session_name( + condition, &condition_session_name); + assert((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); + + if (strcmp(channel_info->session_info->name, condition_session_name)) { goto fail; } - status = lttng_condition_buffer_usage_get_session_name( - condition, &trigger_session_name); - assert((status == LTTNG_CONDITION_STATUS_OK) && trigger_session_name); + return true; +fail: + return false; +} - status = lttng_condition_buffer_usage_get_channel_name( - condition, &trigger_channel_name); - assert((status == LTTNG_CONDITION_STATUS_OK) && trigger_channel_name); +static +bool trigger_applies_to_channel(struct lttng_trigger *trigger, + struct channel_info *channel_info) +{ + struct lttng_condition *condition; + bool trigger_applies; - if (strcmp(info->session_name, trigger_session_name)) { + condition = lttng_trigger_get_condition(trigger); + if (!condition) { goto fail; } - if (strcmp(info->channel_name, trigger_channel_name)) { + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + trigger_applies = buffer_usage_condition_applies_to_channel( + condition, channel_info); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + trigger_applies = session_consumed_size_condition_applies_to_channel( + condition, channel_info); + break; + default: goto fail; } - return true; + return trigger_applies; fail: return false; } @@ -633,38 +984,93 @@ bool trigger_applies_to_client(struct lttng_trigger *trigger, } static -unsigned long hash_channel_key(struct channel_key *key) +int match_session(struct cds_lfht_node *node, const void *key) { - return hash_key_u64(&key->key, lttng_ht_seed) ^ hash_key_ulong( - (void *) (unsigned long) key->domain, lttng_ht_seed); + const char *name = key; + struct session_info *session_info = caa_container_of( + node, struct session_info, sessions_ht_node); + + return !strcmp(session_info->name, name); +} + +static +struct session_info *find_or_create_session_info( + struct notification_thread_state *state, + const char *name, uid_t uid, gid_t gid) +{ + struct session_info *session = NULL; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + + rcu_read_lock(); + cds_lfht_lookup(state->sessions_ht, + hash_key_str(name, lttng_ht_seed), + match_session, + name, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + DBG("[notification-thread] Found session info of session \"%s\" (uid = %i, gid = %i)", + name, uid, gid); + session = caa_container_of(node, struct session_info, + sessions_ht_node); + assert(session->uid == uid); + assert(session->gid == gid); + goto end; + } + + session = session_info_create(name, uid, gid); + if (!session) { + ERR("[notification-thread] Failed to allocation session info for session \"%s\" (uid = %i, gid = %i)", + name, uid, gid); + goto end; + } + + cds_lfht_add(state->sessions_ht, hash_key_str(name, lttng_ht_seed), + &session->sessions_ht_node); +end: + rcu_read_unlock(); + return session; } static int handle_notification_thread_command_add_channel( - struct notification_thread_state *state, - struct channel_info *channel_info, - enum lttng_error_code *cmd_result) + struct notification_thread_state *state, + const char *session_name, uid_t session_uid, gid_t session_gid, + const char *channel_name, enum lttng_domain_type channel_domain, + uint64_t channel_key_int, uint64_t channel_capacity, + enum lttng_error_code *cmd_result) { struct cds_list_head trigger_list; - struct channel_info *new_channel_info; - struct channel_key *channel_key; + struct channel_info *new_channel_info = NULL; + struct channel_key channel_key = { + .key = channel_key_int, + .domain = channel_domain, + }; struct lttng_channel_trigger_list *channel_trigger_list = NULL; struct lttng_trigger_ht_element *trigger_ht_element = NULL; int trigger_count = 0; struct cds_lfht_iter iter; + struct session_info *session_info = NULL; DBG("[notification-thread] Adding channel %s from session %s, channel key = %" PRIu64 " in %s domain", - channel_info->channel_name, channel_info->session_name, - channel_info->key.key, channel_info->key.domain == LTTNG_DOMAIN_KERNEL ? "kernel" : "user space"); + channel_name, session_name, channel_key_int, + channel_domain == LTTNG_DOMAIN_KERNEL ? "kernel" : "user space"); CDS_INIT_LIST_HEAD(&trigger_list); - new_channel_info = channel_info_copy(channel_info); - if (!new_channel_info) { + session_info = find_or_create_session_info(state, session_name, + session_uid, session_gid); + if (!session_info) { + /* Allocation error or an internal error occured. */ goto error; } - channel_key = &new_channel_info->key; + new_channel_info = channel_info_create(channel_name, &channel_key, + channel_capacity, session_info); + if (!new_channel_info) { + goto error; + } /* Build a list of all triggers applying to the new channel. */ cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, @@ -672,7 +1078,7 @@ int handle_notification_thread_command_add_channel( struct lttng_trigger_list_element *new_element; if (!trigger_applies_to_channel(trigger_ht_element->trigger, - channel_info)) { + new_channel_info)) { continue; } @@ -692,7 +1098,7 @@ int handle_notification_thread_command_add_channel( if (!channel_trigger_list) { goto error; } - channel_trigger_list->channel_key = *channel_key; + channel_trigger_list->channel_key = new_channel_info->key; CDS_INIT_LIST_HEAD(&channel_trigger_list->list); cds_lfht_node_init(&channel_trigger_list->channel_triggers_ht_node); cds_list_splice(&trigger_list, &channel_trigger_list->list); @@ -700,21 +1106,21 @@ int handle_notification_thread_command_add_channel( rcu_read_lock(); /* Add channel to the channel_ht which owns the channel_infos. */ cds_lfht_add(state->channels_ht, - hash_channel_key(channel_key), + hash_channel_key(&new_channel_info->key), &new_channel_info->channels_ht_node); /* * Add the list of triggers associated with this channel to the * channel_triggers_ht. */ cds_lfht_add(state->channel_triggers_ht, - hash_channel_key(channel_key), + hash_channel_key(&new_channel_info->key), &channel_trigger_list->channel_triggers_ht_node); rcu_read_unlock(); *cmd_result = LTTNG_OK; return 0; error: - /* Empty trigger list */ channel_info_destroy(new_channel_info); + session_info_put(session_info); return 1; } @@ -800,21 +1206,66 @@ end: return 0; } +static +int condition_is_supported(struct lttng_condition *condition) +{ + int ret; + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + { + enum lttng_domain_type domain; + + ret = lttng_condition_buffer_usage_get_domain_type(condition, + &domain); + if (ret) { + ret = -1; + goto end; + } + + if (domain != LTTNG_DOMAIN_KERNEL) { + ret = 1; + goto end; + } + + /* + * Older kernel tracers don't expose the API to monitor their + * buffers. Therefore, we reject triggers that require that + * mechanism to be available to be evaluated. + */ + ret = kernel_supports_ring_buffer_snapshot_sample_positions( + kernel_tracer_fd); + break; + } + default: + ret = 1; + } +end: + return ret; +} + /* * FIXME A client's credentials are not checked when registering a trigger, nor * are they stored alongside with the trigger. * - * The effects of this are benign: + * The effects of this are benign since: * - The client will succeed in registering the trigger, as it is valid, * - The trigger will, internally, be bound to the channel, * - The notifications will not be sent since the client's credentials * are checked against the channel at that moment. + * + * If this function returns a non-zero value, it means something is + * fundamentally broken and the whole subsystem/thread will be torn down. + * + * If a non-fatal error occurs, just set the cmd_result to the appropriate + * error code. */ static int handle_notification_thread_command_register_trigger( - struct notification_thread_state *state, - struct lttng_trigger *trigger, - enum lttng_error_code *cmd_result) + struct notification_thread_state *state, + struct lttng_trigger *trigger, + enum lttng_error_code *cmd_result) { int ret = 0; struct lttng_condition *condition; @@ -830,6 +1281,19 @@ int handle_notification_thread_command_register_trigger( rcu_read_lock(); condition = lttng_trigger_get_condition(trigger); + assert(condition); + + ret = condition_is_supported(condition); + if (ret < 0) { + goto error; + } else if (ret == 0) { + *cmd_result = LTTNG_ERR_NOT_SUPPORTED; + goto error; + } else { + /* Feature is supported, continue. */ + ret = 0; + } + trigger_ht_element = zmalloc(sizeof(*trigger_ht_element)); if (!trigger_ht_element) { ret = -1; @@ -892,11 +1356,6 @@ int handle_notification_thread_command_register_trigger( cds_lfht_add(state->notification_trigger_clients_ht, lttng_condition_hash(condition), &client_list->notification_trigger_ht_node); - /* - * Client list ownership transferred to the - * notification_trigger_clients_ht. - */ - client_list = NULL; /* * Add the trigger to list of triggers bound to the channels currently @@ -918,7 +1377,6 @@ int handle_notification_thread_command_register_trigger( &iter); node = cds_lfht_iter_get_node(&iter); assert(node); - /* Free the list of triggers associated with this channel. */ trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, channel_triggers_ht_node); @@ -931,10 +1389,49 @@ int handle_notification_thread_command_register_trigger( CDS_INIT_LIST_HEAD(&trigger_list_element->node); trigger_list_element->trigger = trigger; cds_list_add(&trigger_list_element->node, &trigger_list->list); - /* A trigger can only apply to one channel. */ - break; } + /* + * Since there is nothing preventing clients from subscribing to a + * condition before the corresponding trigger is registered, we have + * to evaluate this new condition right away. + * + * At some point, we were waiting for the next "evaluation" (e.g. on + * reception of a channel sample) to evaluate this new condition, but + * that was broken. + * + * The reason it was broken is that waiting for the next sample + * does not allow us to properly handle transitions for edge-triggered + * conditions. + * + * Consider this example: when we handle a new channel sample, we + * evaluate each conditions twice: once with the previous state, and + * again with the newest state. We then use those two results to + * determine whether a state change happened: a condition was false and + * became true. If a state change happened, we have to notify clients. + * + * Now, if a client subscribes to a given notification and registers + * a trigger *after* that subscription, we have to make sure the + * condition is evaluated at this point while considering only the + * current state. Otherwise, the next evaluation cycle may only see + * that the evaluations remain the same (true for samples n-1 and n) and + * the client will never know that the condition has been met. + */ + cds_list_for_each_entry_safe(client_list_element, tmp, + &client_list->list, node) { + ret = evaluate_condition_for_client(trigger, condition, + client_list_element->client, state); + if (ret) { + goto error_free_client_list; + } + } + + /* + * Client list ownership transferred to the + * notification_trigger_clients_ht. + */ + client_list = NULL; + *cmd_result = LTTNG_OK; error_free_client_list: if (client_list) { @@ -997,8 +1494,8 @@ int handle_notification_thread_command_unregister_trigger( cds_list_for_each_entry_safe(trigger_element, tmp, &trigger_list->list, node) { - struct lttng_condition *current_condition = - lttng_trigger_get_condition( + const struct lttng_condition *current_condition = + lttng_trigger_get_const_condition( trigger_element->trigger); assert(current_condition); @@ -1009,6 +1506,8 @@ int handle_notification_thread_command_unregister_trigger( DBG("[notification-thread] Removed trigger from channel_triggers_ht"); cds_list_del(&trigger_element->node); + /* A trigger can only appear once per channel */ + break; } } @@ -1060,8 +1559,9 @@ int handle_notification_thread_command( uint64_t counter; struct notification_thread_command *cmd; - /* Read event_fd to put it back into a quiescent state. */ - ret = read(handle->cmd_queue.event_fd, &counter, sizeof(counter)); + /* Read the event pipe to put it back into a quiescent state. */ + ret = read(lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), &counter, + sizeof(counter)); if (ret == -1) { goto error; } @@ -1085,7 +1585,14 @@ int handle_notification_thread_command( case NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL: DBG("[notification-thread] Received add channel command"); ret = handle_notification_thread_command_add_channel( - state, &cmd->parameters.add_channel, + state, + cmd->parameters.add_channel.session.name, + cmd->parameters.add_channel.session.uid, + cmd->parameters.add_channel.session.gid, + cmd->parameters.add_channel.channel.name, + cmd->parameters.add_channel.channel.domain, + cmd->parameters.add_channel.channel.key, + cmd->parameters.add_channel.channel.capacity, &cmd->reply_code); break; case NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL: @@ -1153,7 +1660,7 @@ end: } static -void client_reset_inbound_state(struct notification_client *client) +int client_reset_inbound_state(struct notification_client *client) { int ret; @@ -1165,9 +1672,12 @@ void client_reset_inbound_state(struct notification_client *client) sizeof(struct lttng_notification_channel_message); client->communication.inbound.msg_type = LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN; - client->communication.inbound.receive_creds = false; LTTNG_SOCK_SET_UID_CRED(&client->communication.inbound.creds, -1); LTTNG_SOCK_SET_GID_CRED(&client->communication.inbound.creds, -1); + ret = lttng_dynamic_buffer_set_size( + &client->communication.inbound.buffer, + client->communication.inbound.bytes_to_receive); + return ret; } int handle_notification_thread_client_connect( @@ -1187,7 +1697,13 @@ int handle_notification_thread_client_connect( CDS_INIT_LIST_HEAD(&client->condition_list); lttng_dynamic_buffer_init(&client->communication.inbound.buffer); lttng_dynamic_buffer_init(&client->communication.outbound.buffer); - client_reset_inbound_state(client); + client->communication.inbound.expect_creds = true; + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + ret = 0; + goto error; + } ret = lttcomm_accept_unix_sock(state->notification_channel_socket); if (ret < 0) { @@ -1291,7 +1807,7 @@ int handle_notification_thread_client_disconnect_all( int handle_notification_thread_trigger_unregister_all( struct notification_thread_state *state) { - bool error_occured = false; + bool error_occurred = false; struct cds_lfht_iter iter; struct lttng_trigger_ht_element *trigger_ht_element; @@ -1300,10 +1816,10 @@ int handle_notification_thread_trigger_unregister_all( int ret = handle_notification_thread_command_unregister_trigger( state, trigger_ht_element->trigger, NULL); if (ret) { - error_occured = true; + error_occurred = true; } } - return error_occured ? -1 : 0; + return error_occurred ? -1 : 0; } static @@ -1476,12 +1992,8 @@ int client_dispatch_message(struct notification_client *client, client->communication.inbound.bytes_to_receive = msg->size; client->communication.inbound.msg_type = (enum lttng_notification_channel_message_type) msg->type; - if (client->communication.inbound.msg_type == - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE) { - client->communication.inbound.receive_creds = true; - } ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, 0); + &client->communication.inbound.buffer, msg->size); if (ret) { goto end; } @@ -1509,7 +2021,7 @@ int client_dispatch_message(struct notification_client *client, handshake_client = (struct lttng_notification_channel_command_handshake *) client->communication.inbound.buffer.data; - client->major = handshake_client->major; + client->major = handshake_client->major; client->minor = handshake_client->minor; if (!client->communication.inbound.creds_received) { ERR("[notification-thread] No credentials received from client"); @@ -1548,7 +2060,11 @@ int client_dispatch_message(struct notification_client *client, } /* Set reception state to receive the next message header. */ - client_reset_inbound_state(client); + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end; + } client->validated = true; break; } @@ -1574,10 +2090,6 @@ int client_dispatch_message(struct notification_client *client, if (client->communication.inbound.msg_type == LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE) { - /* - * FIXME The current state should be evaluated on - * subscription. - */ ret = notification_thread_client_subscribe(client, condition, state, &status); } else { @@ -1595,7 +2107,11 @@ int client_dispatch_message(struct notification_client *client, } /* Set reception state to receive the next message header. */ - client_reset_inbound_state(client); + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end; + } break; } default: @@ -1609,7 +2125,7 @@ end: int handle_notification_thread_client_in( struct notification_thread_state *state, int socket) { - int ret; + int ret = 0; struct notification_client *client; ssize_t recv_ret; size_t offset; @@ -1621,21 +2137,15 @@ int handle_notification_thread_client_in( goto end; } - offset = client->communication.inbound.buffer.size; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, - client->communication.inbound.bytes_to_receive); - if (ret) { - goto end; - } - - if (client->communication.inbound.receive_creds) { + offset = client->communication.inbound.buffer.size - + client->communication.inbound.bytes_to_receive; + if (client->communication.inbound.expect_creds) { recv_ret = lttcomm_recv_creds_unix_sock(socket, client->communication.inbound.buffer.data + offset, client->communication.inbound.bytes_to_receive, &client->communication.inbound.creds); if (recv_ret > 0) { - client->communication.inbound.receive_creds = false; + client->communication.inbound.expect_creds = false; client->communication.inbound.creds_received = true; } } else { @@ -1648,14 +2158,6 @@ int handle_notification_thread_client_in( } client->communication.inbound.bytes_to_receive -= recv_ret; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, - client->communication.inbound.buffer.size - - client->communication.inbound.bytes_to_receive); - if (ret) { - goto end; - } - if (client->communication.inbound.bytes_to_receive == 0) { ret = client_dispatch_message(client, state); if (ret) { @@ -1698,20 +2200,17 @@ end: } static -bool evaluate_buffer_usage_condition(struct lttng_condition *condition, - struct channel_state_sample *sample, uint64_t buffer_capacity) +bool evaluate_buffer_usage_condition(const struct lttng_condition *condition, + const struct channel_state_sample *sample, + uint64_t buffer_capacity) { bool result = false; uint64_t threshold; enum lttng_condition_type condition_type; - struct lttng_condition_buffer_usage *use_condition = container_of( + const struct lttng_condition_buffer_usage *use_condition = container_of( condition, struct lttng_condition_buffer_usage, parent); - if (!sample) { - goto end; - } - if (use_condition->threshold_bytes.set) { threshold = use_condition->threshold_bytes.value; } else { @@ -1755,32 +2254,73 @@ bool evaluate_buffer_usage_condition(struct lttng_condition *condition, result = true; } } -end: + return result; } static -int evaluate_condition(struct lttng_condition *condition, +bool evaluate_session_consumed_size_condition( + const struct lttng_condition *condition, + uint64_t session_consumed_size) +{ + uint64_t threshold; + const struct lttng_condition_session_consumed_size *size_condition = + container_of(condition, + struct lttng_condition_session_consumed_size, + parent); + + threshold = size_condition->consumed_threshold_bytes.value; + DBG("[notification-thread] Session consumed size condition being evaluated: threshold = %" PRIu64 ", current size = %" PRIu64, + threshold, session_consumed_size); + return session_consumed_size >= threshold; +} + +static +int evaluate_condition(const struct lttng_condition *condition, struct lttng_evaluation **evaluation, - struct notification_thread_state *state, - struct channel_state_sample *previous_sample, - struct channel_state_sample *latest_sample, - uint64_t buffer_capacity) + const struct notification_thread_state *state, + const struct channel_state_sample *previous_sample, + const struct channel_state_sample *latest_sample, + uint64_t previous_session_consumed_total, + uint64_t latest_session_consumed_total, + struct channel_info *channel_info) { int ret = 0; enum lttng_condition_type condition_type; - bool previous_sample_result; + const bool previous_sample_available = !!previous_sample; + bool previous_sample_result = false; bool latest_sample_result; condition_type = lttng_condition_get_type(condition); - /* No other condition type supported for the moment. */ - assert(condition_type == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW || - condition_type == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH); - previous_sample_result = evaluate_buffer_usage_condition(condition, - previous_sample, buffer_capacity); - latest_sample_result = evaluate_buffer_usage_condition(condition, - latest_sample, buffer_capacity); + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + if (caa_likely(previous_sample_available)) { + previous_sample_result = + evaluate_buffer_usage_condition(condition, + previous_sample, channel_info->capacity); + } + latest_sample_result = evaluate_buffer_usage_condition( + condition, latest_sample, + channel_info->capacity); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + if (caa_likely(previous_sample_available)) { + previous_sample_result = + evaluate_session_consumed_size_condition( + condition, + previous_session_consumed_total); + } + latest_sample_result = + evaluate_session_consumed_size_condition( + condition, + latest_session_consumed_total); + break; + default: + /* Unknown condition type; internal error. */ + abort(); + } if (!latest_sample_result || (previous_sample_result == latest_sample_result)) { @@ -1793,15 +2333,29 @@ int evaluate_condition(struct lttng_condition *condition, goto end; } - if (evaluation && latest_sample_result) { + if (!evaluation || !latest_sample_result) { + goto end; + } + + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: *evaluation = lttng_evaluation_buffer_usage_create( condition_type, latest_sample->highest_usage, - buffer_capacity); - if (!*evaluation) { - ret = -1; - goto end; - } + channel_info->capacity); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + *evaluation = lttng_evaluation_session_consumed_size_create( + latest_session_consumed_total); + break; + default: + abort(); + } + + if (!*evaluation) { + ret = -1; + goto end; } end: return ret; @@ -1824,8 +2378,8 @@ int client_enqueue_dropped_notification(struct notification_client *client, } static -int send_evaluation_to_clients(struct lttng_trigger *trigger, - struct lttng_evaluation *evaluation, +int send_evaluation_to_clients(const struct lttng_trigger *trigger, + const struct lttng_evaluation *evaluation, struct notification_client_list* client_list, struct notification_thread_state *state, uid_t channel_uid, gid_t channel_gid) @@ -1833,51 +2387,33 @@ int send_evaluation_to_clients(struct lttng_trigger *trigger, int ret = 0; struct lttng_dynamic_buffer msg_buffer; struct notification_client_list_element *client_list_element, *tmp; - struct lttng_notification *notification; - struct lttng_condition *condition; - ssize_t expected_notification_size, notification_size; - struct lttng_notification_channel_message msg; + const struct lttng_notification notification = { + .condition = (struct lttng_condition *) lttng_trigger_get_const_condition(trigger), + .evaluation = (struct lttng_evaluation *) evaluation, + }; + struct lttng_notification_channel_message msg_header = { + .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION, + }; lttng_dynamic_buffer_init(&msg_buffer); - condition = lttng_trigger_get_condition(trigger); - assert(condition); - - notification = lttng_notification_create(condition, evaluation); - if (!notification) { - ret = -1; - goto end; - } - - expected_notification_size = lttng_notification_serialize(notification, - NULL); - if (expected_notification_size < 0) { - ERR("[notification-thread] Failed to get size of serialized notification"); - ret = -1; - goto end; - } - - msg.type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION; - msg.size = (uint32_t) expected_notification_size; - ret = lttng_dynamic_buffer_append(&msg_buffer, &msg, sizeof(msg)); + ret = lttng_dynamic_buffer_append(&msg_buffer, &msg_header, + sizeof(msg_header)); if (ret) { goto end; } - ret = lttng_dynamic_buffer_set_size(&msg_buffer, - msg_buffer.size + expected_notification_size); + ret = lttng_notification_serialize(¬ification, &msg_buffer); if (ret) { - goto end; - } - - notification_size = lttng_notification_serialize(notification, - msg_buffer.data + sizeof(msg)); - if (notification_size != expected_notification_size) { ERR("[notification-thread] Failed to serialize notification"); ret = -1; goto end; } + /* Update payload size. */ + ((struct lttng_notification_channel_message * ) msg_buffer.data)->size = + (uint32_t) (msg_buffer.size - sizeof(msg_header)); + cds_list_for_each_entry_safe(client_list_element, tmp, &client_list->list, node) { struct notification_client *client = @@ -1927,7 +2463,6 @@ int send_evaluation_to_clients(struct lttng_trigger *trigger, } ret = 0; end: - lttng_notification_destroy(notification); lttng_dynamic_buffer_reset(&msg_buffer); return ret; } @@ -1938,13 +2473,14 @@ int handle_notification_thread_channel_sample( { int ret = 0; struct lttcomm_consumer_channel_monitor_msg sample_msg; - struct channel_state_sample previous_sample, latest_sample; struct channel_info *channel_info; struct cds_lfht_node *node; struct cds_lfht_iter iter; struct lttng_channel_trigger_list *trigger_list; struct lttng_trigger_list_element *trigger_list_element; bool previous_sample_available = false; + struct channel_state_sample previous_sample, latest_sample; + uint64_t previous_session_consumed_total, latest_session_consumed_total; /* * The monitoring pipe only holds messages smaller than PIPE_BUF, @@ -1963,6 +2499,7 @@ int handle_notification_thread_channel_sample( latest_sample.key.domain = domain; latest_sample.highest_usage = sample_msg.highest; latest_sample.lowest_usage = sample_msg.lowest; + latest_sample.channel_total_consumed = sample_msg.total_consumed; rcu_read_lock(); @@ -1973,7 +2510,7 @@ int handle_notification_thread_channel_sample( &latest_sample.key, &iter); node = cds_lfht_iter_get_node(&iter); - if (!node) { + if (caa_unlikely(!node)) { /* * Not an error since the consumer can push a sample to the pipe * and the rest of the session daemon could notify us of the @@ -1988,12 +2525,16 @@ int handle_notification_thread_channel_sample( } channel_info = caa_container_of(node, struct channel_info, channels_ht_node); - DBG("[notification-thread] Handling channel sample for channel %s (key = %" PRIu64 ") in session %s (highest usage = %" PRIu64 ", lowest usage = %" PRIu64")", - channel_info->channel_name, + DBG("[notification-thread] Handling channel sample for channel %s (key = %" PRIu64 ") in session %s (highest usage = %" PRIu64 ", lowest usage = %" PRIu64", total consumed = %" PRIu64")", + channel_info->name, latest_sample.key.key, - channel_info->session_name, + channel_info->session_info->name, latest_sample.highest_usage, - latest_sample.lowest_usage); + latest_sample.lowest_usage, + latest_sample.channel_total_consumed); + + previous_session_consumed_total = + channel_info->session_info->consumed_data_size; /* Retrieve the channel's last sample, if it exists, and update it. */ cds_lfht_lookup(state->channel_state_ht, @@ -2002,18 +2543,24 @@ int handle_notification_thread_channel_sample( &latest_sample.key, &iter); node = cds_lfht_iter_get_node(&iter); - if (node) { + if (caa_likely(node)) { struct channel_state_sample *stored_sample; /* Update the sample stored. */ stored_sample = caa_container_of(node, struct channel_state_sample, channel_state_ht_node); + memcpy(&previous_sample, stored_sample, sizeof(previous_sample)); stored_sample->highest_usage = latest_sample.highest_usage; stored_sample->lowest_usage = latest_sample.lowest_usage; + stored_sample->channel_total_consumed = latest_sample.channel_total_consumed; previous_sample_available = true; + + latest_session_consumed_total = + previous_session_consumed_total + + (latest_sample.channel_total_consumed - previous_sample.channel_total_consumed); } else { /* * This is the channel's first sample, allocate space for and @@ -2032,8 +2579,15 @@ int handle_notification_thread_channel_sample( cds_lfht_add(state->channel_state_ht, hash_channel_key(&stored_sample->key), &stored_sample->channel_state_ht_node); + + latest_session_consumed_total = + previous_session_consumed_total + + latest_sample.channel_total_consumed; } + channel_info->session_info->consumed_data_size = + latest_session_consumed_total; + /* Find triggers associated with this channel. */ cds_lfht_lookup(state->channel_triggers_ht, hash_channel_key(&latest_sample.key), @@ -2041,7 +2595,7 @@ int handle_notification_thread_channel_sample( &latest_sample.key, &iter); node = cds_lfht_iter_get_node(&iter); - if (!node) { + if (caa_likely(!node)) { goto end_unlock; } @@ -2049,19 +2603,19 @@ int handle_notification_thread_channel_sample( channel_triggers_ht_node); cds_list_for_each_entry(trigger_list_element, &trigger_list->list, node) { - struct lttng_condition *condition; - struct lttng_action *action; - struct lttng_trigger *trigger; + const struct lttng_condition *condition; + const struct lttng_action *action; + const struct lttng_trigger *trigger; struct notification_client_list *client_list; struct lttng_evaluation *evaluation = NULL; trigger = trigger_list_element->trigger; - condition = lttng_trigger_get_condition(trigger); + condition = lttng_trigger_get_const_condition(trigger); assert(condition); - action = lttng_trigger_get_action(trigger); + action = lttng_trigger_get_const_action(trigger); /* Notify actions are the only type currently supported. */ - assert(lttng_action_get_type(action) == + assert(lttng_action_get_type_const(action) == LTTNG_ACTION_TYPE_NOTIFY); /* @@ -2089,20 +2643,25 @@ int handle_notification_thread_channel_sample( ret = evaluate_condition(condition, &evaluation, state, previous_sample_available ? &previous_sample : NULL, - &latest_sample, channel_info->capacity); - if (ret) { + &latest_sample, + previous_session_consumed_total, + latest_session_consumed_total, + channel_info); + if (caa_unlikely(ret)) { goto end_unlock; } - if (!evaluation) { + if (caa_likely(!evaluation)) { continue; } /* Dispatch evaluation result to all clients. */ ret = send_evaluation_to_clients(trigger_list_element->trigger, evaluation, client_list, state, - channel_info->uid, channel_info->gid); - if (ret) { + channel_info->session_info->uid, + channel_info->session_info->gid); + lttng_evaluation_destroy(evaluation); + if (caa_unlikely(ret)) { goto end_unlock; } }