X-Git-Url: http://git.liburcu.org/?a=blobdiff_plain;f=ltt-events.c;h=1d729210f83049ba3fcc7f5364988afedcfaf901;hb=cd4bd11fe2d3232089186a5b9b021b7cb504a8fb;hp=f22351fcb0f92fc83070f3a0b90009ee3adba7ab;hpb=baf2099544522f85e52c830b13aa45601ff15204;p=lttng-modules.git diff --git a/ltt-events.c b/ltt-events.c index f22351fc..1d729210 100644 --- a/ltt-events.c +++ b/ltt-events.c @@ -7,13 +7,30 @@ */ #include +#include +#include +#include +#include +#include +#include "wrapper/vmalloc.h" /* for wrapper_vmalloc_sync_all() */ #include "ltt-events.h" +#include "ltt-tracer.h" static LIST_HEAD(sessions); +static LIST_HEAD(ltt_transport_list); static DEFINE_MUTEX(sessions_mutex); static struct kmem_cache *event_cache; -static void synchronize_trace(void) +static +int _ltt_event_metadata_statedump(struct ltt_session *session, + struct ltt_channel *chan, + struct ltt_event *event); +static +int _ltt_session_metadata_statedump(struct ltt_session *session); + + +static +void synchronize_trace(void) { synchronize_sched(); #ifdef CONFIG_PREEMPT_RT @@ -26,26 +43,28 @@ struct ltt_session *ltt_session_create(void) struct ltt_session *session; mutex_lock(&sessions_mutex); - session = kmalloc(sizeof(struct ltt_session)); + session = kzalloc(sizeof(struct ltt_session), GFP_KERNEL); if (!session) return NULL; INIT_LIST_HEAD(&session->chan); + INIT_LIST_HEAD(&session->events); list_add(&session->list, &sessions); mutex_unlock(&sessions_mutex); return session; - -exist: - mutex_unlock(&sessions_mutex); - return NULL; } -int ltt_session_destroy(struct ltt_session *session) +void ltt_session_destroy(struct ltt_session *session) { struct ltt_channel *chan, *tmpchan; struct ltt_event *event, *tmpevent; + int ret; mutex_lock(&sessions_mutex); - session->active = 0; + ACCESS_ONCE(session->active) = 0; + list_for_each_entry(event, &session->events, list) { + ret = _ltt_event_unregister(event); + WARN_ON(ret); + } synchronize_trace(); /* Wait for in-flight events to complete */ list_for_each_entry_safe(event, tmpevent, &session->events, list) _ltt_event_destroy(event); @@ -56,28 +75,110 @@ int ltt_session_destroy(struct ltt_session *session) kfree(session); } +int ltt_session_start(struct ltt_session *session) +{ + int ret = 0; + struct ltt_channel *chan; + + mutex_lock(&sessions_mutex); + if (session->active) { + ret = -EBUSY; + goto end; + } + + /* + * Snapshot the number of events per channel to know the type of header + * we need to use. + */ + list_for_each_entry(chan, &session->chan, list) { + if (chan->header_type) + continue; /* don't change it if session stop/restart */ + if (chan->free_event_id < 31) + chan->header_type = 1; /* compact */ + else + chan->header_type = 2; /* large */ + } + + ACCESS_ONCE(session->active) = 1; + synchronize_trace(); /* Wait for in-flight events to complete */ + ret = _ltt_session_metadata_statedump(session); + if (ret) { + ACCESS_ONCE(session->active) = 0; + synchronize_trace(); /* Wait for in-flight events to complete */ + } +end: + mutex_unlock(&sessions_mutex); + return ret; +} + +int ltt_session_stop(struct ltt_session *session) +{ + int ret = 0; + + mutex_lock(&sessions_mutex); + if (!session->active) { + ret = -EBUSY; + goto end; + } + ACCESS_ONCE(session->active) = 0; + synchronize_trace(); /* Wait for in-flight events to complete */ +end: + mutex_unlock(&sessions_mutex); + return ret; +} + +static struct ltt_transport *ltt_transport_find(const char *name) +{ + struct ltt_transport *transport; + + list_for_each_entry(transport, <t_transport_list, node) { + if (!strcmp(transport->name, name)) + return transport; + } + return NULL; +} + struct ltt_channel *ltt_channel_create(struct ltt_session *session, - int overwrite, void *buf_addr, + const char *transport_name, + void *buf_addr, size_t subbuf_size, size_t num_subbuf, unsigned int switch_timer_interval, unsigned int read_timer_interval) { struct ltt_channel *chan; + struct ltt_transport *transport; mutex_lock(&sessions_mutex); - if (session->active) + if (session->active) { + printk(KERN_WARNING "LTTng refusing to add channel to active session\n"); goto active; /* Refuse to add channel to active session */ - chan = kmalloc(sizeof(struct ltt_channel), GFP_KERNEL); + } + transport = ltt_transport_find(transport_name); + if (!transport) { + printk(KERN_WARNING "LTTng transport %s not found\n", + transport_name); + goto notransport; + } + chan = kzalloc(sizeof(struct ltt_channel), GFP_KERNEL); if (!chan) - return NULL; + goto nomem; chan->session = session; - - /* TODO: create rb channel */ + init_waitqueue_head(&chan->notify_wait); + chan->chan = transport->ops.channel_create("[lttng]", session, buf_addr, + subbuf_size, num_subbuf, switch_timer_interval, + read_timer_interval); + if (!chan->chan) + goto create_error; + chan->id = session->free_chan_id++; + chan->ops = &transport->ops; list_add(&chan->list, &session->chan); mutex_unlock(&sessions_mutex); return chan; -exist: +create_error: + kfree(chan); +nomem: +notransport: active: mutex_unlock(&sessions_mutex); return NULL; @@ -86,9 +187,9 @@ active: /* * Only used internally at session destruction. */ -int _ltt_channel_destroy(struct ltt_channel *chan) +void _ltt_channel_destroy(struct ltt_channel *chan) { - /* TODO: destroy rb channel */ + chan->ops->channel_destroy(chan->chan); list_del(&chan->list); kfree(chan); } @@ -98,7 +199,8 @@ int _ltt_channel_destroy(struct ltt_channel *chan) */ struct ltt_event *ltt_event_create(struct ltt_channel *chan, char *name, enum instrum_type itype, - void *probe, void *filter) + const struct lttng_event_desc *event_desc, + void *filter) { struct ltt_event *event; int ret; @@ -107,38 +209,44 @@ struct ltt_event *ltt_event_create(struct ltt_channel *chan, char *name, if (chan->free_event_id == -1UL) goto full; /* - * This is O(n^2) (for each event loop called at event creation). - * Might require a hash if we have lots of events. + * This is O(n^2) (for each event, the loop is called at event + * creation). Might require a hash if we have lots of events. */ list_for_each_entry(event, &chan->session->events, list) - if (!strcmp(event->name, name)) + if (!strcmp(event->desc->name, name)) goto exist; - event = kmem_cache_zalloc(events_cache, GFP_KERNEL); + event = kmem_cache_zalloc(event_cache, GFP_KERNEL); if (!event) goto cache_error; - event->name = kmalloc(strlen(name) + 1, GFP_KERNEL); - if (!event->name) - goto error; - strcpy(event->name, name); event->chan = chan; - event->probe = probe; + event->desc = event_desc; event->filter = filter; event->id = chan->free_event_id++; event->itype = itype; - mutex_unlock(&sessions_mutex); /* Populate ltt_event structure before tracepoint registration. */ smp_wmb(); switch (itype) { case INSTRUM_TRACEPOINTS: - ret = tracepoint_probe_register(name, probe, event); + ret = tracepoint_probe_register(name, event_desc->probe_callback, + event); + if (ret) + goto register_error; break; default: WARN_ON_ONCE(1); } + ret = _ltt_event_metadata_statedump(chan->session, chan, event); + if (ret) + goto statedump_error; + list_add(&event->list, &chan->session->events); + mutex_unlock(&sessions_mutex); return event; -error: - kmem_cache_free(event); +statedump_error: + WARN_ON_ONCE(tracepoint_probe_unregister(name, event_desc->probe_callback, + event)); +register_error: + kmem_cache_free(event_cache, event); cache_error: exist: full: @@ -149,43 +257,354 @@ full: /* * Only used internally at session destruction. */ -int _ltt_event_destroy(struct ltt_event *event) +int _ltt_event_unregister(struct ltt_event *event) { + int ret = -EINVAL; + switch (event->itype) { case INSTRUM_TRACEPOINTS: - ret = tracepoint_probe_unregister(name, event->probe, event); + ret = tracepoint_probe_unregister(event->desc->name, + event->desc->probe_callback, + event); + if (ret) + return ret; break; default: WARN_ON_ONCE(1); } - kfree(event->name); - kmem_cache_free(event); + return ret; } -static int __init ltt_events_init(void) +/* + * Only used internally at session destruction. + */ +void _ltt_event_destroy(struct ltt_event *event) { - int ret; + ltt_event_put(event->desc); + list_del(&event->list); + kmem_cache_free(event_cache, event); +} + +int lttng_metadata_printf(struct ltt_session *session, + const char *fmt, ...) +{ + struct lib_ring_buffer_ctx ctx; + struct ltt_channel *chan = session->metadata; + char *str; + int ret = 0, waitret; + size_t len; + va_list ap; + + WARN_ON_ONCE(!ACCESS_ONCE(session->active)); - events_cache = KMEM_CACHE(ltt_event, 0); - if (!events_cache) + va_start(ap, fmt); + str = kvasprintf(GFP_KERNEL, fmt, ap); + va_end(ap); + if (!str) return -ENOMEM; - /* TODO: show ABI to userspace */ + len = strlen(str) + 1; + lib_ring_buffer_ctx_init(&ctx, chan->chan, NULL, len, sizeof(char), -1); + /* + * We don't care about metadata buffer's records lost count, because we + * always retry here. Report error if we need to bail out after timeout + * or being interrupted. + */ + waitret = wait_event_interruptible_timeout(*chan->ops->get_reader_wait_queue(chan), + ({ + ret = chan->ops->event_reserve(&ctx); + ret != -ENOBUFS || !ret; + }), + msecs_to_jiffies(LTTNG_METADATA_TIMEOUT_MSEC)); + if (waitret || ret) { + printk(KERN_WARNING "LTTng: Failure to write metadata to buffers (%s)\n", + waitret == -ERESTARTSYS ? "interrupted" : + (ret == -ENOBUFS ? "timeout" : "I/O error")); + if (waitret == -ERESTARTSYS) + ret = waitret; + goto end; + } + chan->ops->event_write(&ctx, str, len); + chan->ops->event_commit(&ctx); +end: + kfree(str); + return ret; +} + +static +int _ltt_fields_metadata_statedump(struct ltt_session *session, + struct ltt_event *event) +{ + const struct lttng_event_desc *desc = event->desc; + int ret = 0; + int i; + + for (i = 0; i < desc->nr_fields; i++) { + const struct lttng_event_field *field = &desc->fields[i]; + + switch (field->type.atype) { + case atype_integer: + ret = lttng_metadata_printf(session, + " integer { size = %u; align = %u; signed = %u;%s } %s;\n", + field->type.u.basic.integer.size, + field->type.u.basic.integer.alignment, + field->type.u.basic.integer.signedness, +#ifdef __BIG_ENDIAN + field->type.u.basic.integer.reverse_byte_order ? " byte_order = le;" : "", +#else + field->type.u.basic.integer.reverse_byte_order ? " byte_order = be;" : "", +#endif + field->name); + break; + case atype_enum: + ret = lttng_metadata_printf(session, + " %s %s;\n", + field->type.u.basic.enumeration.name, + field->name); + break; + case atype_array: + { + const struct lttng_basic_type *elem_type; + elem_type = &field->type.u.array.elem_type; + ret = lttng_metadata_printf(session, + " integer { size = %u; align = %u; signed = %u;%s } %s[%u];\n", + elem_type->u.basic.integer.size, + elem_type->u.basic.integer.alignment, + elem_type->u.basic.integer.signedness, +#ifdef __BIG_ENDIAN + elem_type->u.basic.integer.reverse_byte_order ? " byte_order = le;" : "", +#else + elem_type->u.basic.integer.reverse_byte_order ? " byte_order = be;" : "", +#endif + field->name, field->type.u.array.length); + break; + } + case atype_sequence: + { + const struct lttng_basic_type *elem_type; + const struct lttng_basic_type *length_type; + + elem_type = &field->type.u.sequence.elem_type; + length_type = &field->type.u.sequence.length_type; + ret = lttng_metadata_printf(session, + " integer { size = %u; align = %u; signed = %u;%s } %s[ integer { size = %u; align = %u; signed = %u;%s } ];\n", + elem_type->u.basic.integer.size, + elem_type->u.basic.integer.alignment, + elem_type->u.basic.integer.signedness, +#ifdef __BIG_ENDIAN + elem_type->u.basic.integer.reverse_byte_order ? " byte_order = le;" : "", +#else + elem_type->u.basic.integer.reverse_byte_order ? " byte_order = be;" : "", +#endif + field->name, + length_type->u.basic.integer.size, + length_type->u.basic.integer.alignment, + length_type->u.basic.integer.signedness, +#ifdef __BIG_ENDIAN + length_type->u.basic.integer.reverse_byte_order ? " byte_order = le;" : "", +#else + length_type->u.basic.integer.reverse_byte_order ? " byte_order = be;" : "" +#endif + ); + break; + } + + case atype_string: + ret = lttng_metadata_printf(session, + " string%s %s;\n", + field->type.u.basic.string.encoding == lttng_encode_ASCII ? + " { encoding = ASCII; }" : "", + field->name); + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; + } + } + return ret; +} + +static +int _ltt_event_metadata_statedump(struct ltt_session *session, + struct ltt_channel *chan, + struct ltt_event *event) +{ + int ret = 0; + + if (event->metadata_dumped || !ACCESS_ONCE(session->active)) + return 0; + if (chan == session->metadata) + return 0; + + ret = lttng_metadata_printf(session, + "event {\n" + " name = %s;\n" + " id = %u;\n" + " stream_id = %u;\n" + " event.fields := struct {\n", + event->desc->name, + event->id, + event->chan->id); + if (ret) + goto end; + + ret = _ltt_fields_metadata_statedump(session, event); + if (ret) + goto end; + + /* + * LTTng space reservation can only reserve multiples of the + * byte size. + */ + ret = lttng_metadata_printf(session, + " } aligned(%u);\n" + "};\n", ltt_get_header_alignment()); + if (ret) + goto end; + + + + + event->metadata_dumped = 1; +end: + return ret; + +} + +static +int _ltt_channel_metadata_statedump(struct ltt_session *session, + struct ltt_channel *chan) +{ + int ret = 0; + + if (chan->metadata_dumped || !ACCESS_ONCE(session->active)) + return 0; + if (chan == session->metadata) + return 0; + + WARN_ON_ONCE(!chan->header_type); + ret = lttng_metadata_printf(session, + "stream {\n" + " id = %u;\n" + " event.header := %s;\n", + "};\n", + chan->id, + chan->header_type == 1 ? "struct event_header_compact" : + "struct event_header_large"); + if (ret) + goto end; + + chan->metadata_dumped = 1; +end: + return ret; +} + +/* + * Output metadata into this session's metadata buffers. + */ +static +int _ltt_session_metadata_statedump(struct ltt_session *session) +{ + struct ltt_channel *chan; + struct ltt_event *event; + int ret = 0; + + if (!ACCESS_ONCE(session->active)) + return 0; + if (session->metadata_dumped) + goto skip_session; + if (!session->metadata) { + printk(KERN_WARNING "LTTng: attempt to start tracing, but metadata channel is not found. Operation abort.\n"); + return -EPERM; + } + +skip_session: + list_for_each_entry(chan, &session->chan, list) { + ret = _ltt_channel_metadata_statedump(session, chan); + if (ret) + goto end; + } + + list_for_each_entry(event, &session->events, list) { + ret = _ltt_event_metadata_statedump(session, chan, event); + if (ret) + goto end; + } + session->metadata_dumped = 1; +end: + return ret; +} + +/** + * ltt_transport_register - LTT transport registration + * @transport: transport structure + * + * Registers a transport which can be used as output to extract the data out of + * LTTng. The module calling this registration function must ensure that no + * trap-inducing code will be executed by the transport functions. E.g. + * vmalloc_sync_all() must be called between a vmalloc and the moment the memory + * is made visible to the transport function. This registration acts as a + * vmalloc_sync_all. Therefore, only if the module allocates virtual memory + * after its registration must it synchronize the TLBs. + */ +void ltt_transport_register(struct ltt_transport *transport) +{ + /* + * Make sure no page fault can be triggered by the module about to be + * registered. We deal with this here so we don't have to call + * vmalloc_sync_all() in each module's init. + */ + wrapper_vmalloc_sync_all(); + + mutex_lock(&sessions_mutex); + list_add_tail(&transport->node, <t_transport_list); + mutex_unlock(&sessions_mutex); +} +EXPORT_SYMBOL_GPL(ltt_transport_register); + +/** + * ltt_transport_unregister - LTT transport unregistration + * @transport: transport structure + */ +void ltt_transport_unregister(struct ltt_transport *transport) +{ + mutex_lock(&sessions_mutex); + list_del(&transport->node); + mutex_unlock(&sessions_mutex); +} +EXPORT_SYMBOL_GPL(ltt_transport_unregister); + +static int __init ltt_events_init(void) +{ + int ret; + + event_cache = KMEM_CACHE(ltt_event, 0); + if (!event_cache) + return -ENOMEM; + ret = ltt_debugfs_abi_init(); + if (ret) + goto error_abi; return 0; +error_abi: + kmem_cache_destroy(event_cache); + return ret; } +module_init(ltt_events_init); + static void __exit ltt_events_exit(void) { struct ltt_session *session, *tmpsession; - /* TODO: hide ABI from userspace, wait for callers to release refs. */ - + ltt_debugfs_abi_exit(); list_for_each_entry_safe(session, tmpsession, &sessions, list) ltt_session_destroy(session); - kmem_cache_destroy(events_cache); + kmem_cache_destroy(event_cache); } +module_exit(ltt_events_exit); + MODULE_LICENSE("GPL and additional rights"); MODULE_AUTHOR("Mathieu Desnoyers "); MODULE_DESCRIPTION("LTTng Events");