X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Famqp.c;h=c9e46c45df0cac7ef0e0cc4f3bdd2e1bda8ad96e;hb=1126617bebe569dffe6b7a5d3f0c400134b41d65;hp=eb54795069f1bcc17f39d2571981d50137f49e99;hpb=cacfb7e267c2d39c03d49a0157fc6cf23940126d;p=collectd.git diff --git a/src/amqp.c b/src/amqp.c index eb547950..c9e46c45 100644 --- a/src/amqp.c +++ b/src/amqp.c @@ -1,7 +1,7 @@ /** * collectd - src/amqp.c - * Copyright (C) 2009 Sebastien Pahl - * Copyright (C) 2010 Florian Forster + * Copyright (C) 2009 Sebastien Pahl + * Copyright (C) 2010-2012 Florian Forster * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -26,16 +26,14 @@ * Florian Forster **/ -#include -#include -#include -#include -#include - #include "collectd.h" #include "common.h" #include "plugin.h" +#include "utils_cmd_putval.h" #include "utils_format_json.h" +#include "utils_format_graphite.h" + +#include #include #include @@ -45,6 +43,10 @@ #define CAMQP_DM_VOLATILE 1 #define CAMQP_DM_PERSISTENT 2 +#define CAMQP_FORMAT_COMMAND 1 +#define CAMQP_FORMAT_JSON 2 +#define CAMQP_FORMAT_GRAPHITE 3 + #define CAMQP_CHANNEL 1 /* @@ -62,11 +64,16 @@ struct camqp_config_s char *password; char *exchange; - char *routingkey; + char *routing_key; /* publish only */ uint8_t delivery_mode; _Bool store_rates; + int format; + /* publish & graphite format only */ + char *prefix; + char *postfix; + char escape_char; /* subscribe only */ char *exchange_type; @@ -85,7 +92,6 @@ static const char *def_vhost = "/"; static const char *def_user = "guest"; static const char *def_password = "guest"; static const char *def_exchange = "amq.fanout"; -static const char *def_routingkey = "collectd"; static pthread_t *subscriber_threads = NULL; static size_t subscriber_threads_num = 0; @@ -128,7 +134,10 @@ static void camqp_config_free (void *ptr) /* {{{ */ sfree (conf->exchange); sfree (conf->exchange_type); sfree (conf->queue); - sfree (conf->routingkey); + sfree (conf->routing_key); + sfree (conf->prefix); + sfree (conf->postfix); + sfree (conf); } /* }}} void camqp_config_free */ @@ -178,8 +187,13 @@ static char *camqp_strerror (camqp_config_t *conf, /* {{{ */ break; case AMQP_RESPONSE_LIBRARY_EXCEPTION: +#if HAVE_AMQP_RPC_REPLY_T_LIBRARY_ERRNO if (r.library_errno) return (sstrerror (r.library_errno, buffer, buffer_size)); +#else + if (r.library_error) + return (sstrerror (r.library_error, buffer, buffer_size)); +#endif else sstrncpy (buffer, "End of stream", sizeof (buffer)); break; @@ -216,6 +230,78 @@ static char *camqp_strerror (camqp_config_t *conf, /* {{{ */ return (buffer); } /* }}} char *camqp_strerror */ +#if HAVE_AMQP_RPC_REPLY_T_LIBRARY_ERRNO +static int camqp_create_exchange (camqp_config_t *conf) /* {{{ */ +{ + amqp_exchange_declare_ok_t *ed_ret; + + if (conf->exchange_type == NULL) + return (0); + + ed_ret = amqp_exchange_declare (conf->connection, + /* channel = */ CAMQP_CHANNEL, + /* exchange = */ amqp_cstring_bytes (conf->exchange), + /* type = */ amqp_cstring_bytes (conf->exchange_type), + /* passive = */ 0, + /* durable = */ 0, + /* auto_delete = */ 1, + /* arguments = */ AMQP_EMPTY_TABLE); + if ((ed_ret == NULL) && camqp_is_error (conf)) + { + char errbuf[1024]; + ERROR ("amqp plugin: amqp_exchange_declare failed: %s", + camqp_strerror (conf, errbuf, sizeof (errbuf))); + camqp_close_connection (conf); + return (-1); + } + + INFO ("amqp plugin: Successfully created exchange \"%s\" " + "with type \"%s\".", + conf->exchange, conf->exchange_type); + + return (0); +} /* }}} int camqp_create_exchange */ +#else +static int camqp_create_exchange (camqp_config_t *conf) /* {{{ */ +{ + amqp_exchange_declare_ok_t *ed_ret; + amqp_table_t argument_table; + struct amqp_table_entry_t_ argument_table_entries[1]; + + if (conf->exchange_type == NULL) + return (0); + + /* Valid arguments: "auto_delete", "internal" */ + argument_table.num_entries = STATIC_ARRAY_SIZE (argument_table_entries); + argument_table.entries = argument_table_entries; + argument_table_entries[0].key = amqp_cstring_bytes ("auto_delete"); + argument_table_entries[0].value.kind = AMQP_FIELD_KIND_BOOLEAN; + argument_table_entries[0].value.value.boolean = 1; + + ed_ret = amqp_exchange_declare (conf->connection, + /* channel = */ CAMQP_CHANNEL, + /* exchange = */ amqp_cstring_bytes (conf->exchange), + /* type = */ amqp_cstring_bytes (conf->exchange_type), + /* passive = */ 0, + /* durable = */ 0, + /* arguments = */ argument_table); + if ((ed_ret == NULL) && camqp_is_error (conf)) + { + char errbuf[1024]; + ERROR ("amqp plugin: amqp_exchange_declare failed: %s", + camqp_strerror (conf, errbuf, sizeof (errbuf))); + camqp_close_connection (conf); + return (-1); + } + + INFO ("amqp plugin: Successfully created exchange \"%s\" " + "with type \"%s\".", + conf->exchange, conf->exchange_type); + + return (0); +} /* }}} int camqp_create_exchange */ +#endif + static int camqp_setup_queue (camqp_config_t *conf) /* {{{ */ { amqp_queue_declare_ok_t *qd_ret; @@ -257,42 +343,14 @@ static int camqp_setup_queue (camqp_config_t *conf) /* {{{ */ { amqp_queue_bind_ok_t *qb_ret; - /* create the exchange */ - if (conf->exchange_type != NULL) - { - amqp_exchange_declare_ok_t *ed_ret; - - ed_ret = amqp_exchange_declare (conf->connection, - /* channel = */ CAMQP_CHANNEL, - /* exchange = */ amqp_cstring_bytes (conf->exchange), - /* type = */ amqp_cstring_bytes (conf->exchange_type), - /* passive = */ 0, - /* durable = */ 0, - /* auto_delete = */ 1, - /* arguments = */ AMQP_EMPTY_TABLE); - if ((ed_ret == NULL) && camqp_is_error (conf)) - { - char errbuf[1024]; - ERROR ("amqp plugin: amqp_exchange_declare failed: %s", - camqp_strerror (conf, errbuf, sizeof (errbuf))); - camqp_close_connection (conf); - return (-1); - } - } - - DEBUG ("amqp plugin: queue = %s; exchange = %s; routing_key = %s;", - conf->queue, conf->exchange, CONF (conf, routingkey)); - assert (conf->queue != NULL); qb_ret = amqp_queue_bind (conf->connection, /* channel = */ CAMQP_CHANNEL, /* queue = */ amqp_cstring_bytes (conf->queue), /* exchange = */ amqp_cstring_bytes (conf->exchange), -#if 1 - /* routing_key = */ amqp_cstring_bytes (CONF (conf, routingkey)), -#else - /* routing_key = */ AMQP_EMPTY_BYTES, -#endif + /* routing_key = */ (conf->routing_key != NULL) + ? amqp_cstring_bytes (conf->routing_key) + : AMQP_EMPTY_BYTES, /* arguments = */ AMQP_EMPTY_TABLE); if ((qb_ret == NULL) && camqp_is_error (conf)) { @@ -313,7 +371,9 @@ static int camqp_setup_queue (camqp_config_t *conf) /* {{{ */ /* consumer_tag = */ AMQP_EMPTY_BYTES, /* no_local = */ 0, /* no_ack = */ 1, - /* exclusive = */ 0); + /* exclusive = */ 0, + /* arguments = */ AMQP_EMPTY_TABLE + ); if ((cm_ret == NULL) && camqp_is_error (conf)) { char errbuf[1024]; @@ -387,13 +447,45 @@ static int camqp_connect (camqp_config_t *conf) /* {{{ */ INFO ("amqp plugin: Successfully opened connection to vhost \"%s\" " "on %s:%i.", CONF(conf, vhost), CONF(conf, host), conf->port); + status = camqp_create_exchange (conf); + if (status != 0) + return (status); + if (!conf->publish) return (camqp_setup_queue (conf)); return (0); } /* }}} int camqp_connect */ +static int camqp_shutdown (void) /* {{{ */ +{ + size_t i; + + DEBUG ("amqp plugin: Shutting down %zu subscriber threads.", + subscriber_threads_num); + + subscriber_threads_running = 0; + for (i = 0; i < subscriber_threads_num; i++) + { + /* FIXME: Sending a signal is not very elegant here. Maybe find out how + * to use a timeout in the thread and check for the variable in regular + * intervals. */ + pthread_kill (subscriber_threads[i], SIGTERM); + pthread_join (subscriber_threads[i], /* retval = */ NULL); + } + + subscriber_threads_num = 0; + sfree (subscriber_threads); + + DEBUG ("amqp plugin: All subscriber threads exited."); + + return (0); +} /* }}} int camqp_shutdown */ + +/* + * Subscribing code + */ static int camqp_read_body (camqp_config_t *conf, /* {{{ */ - size_t body_size) + size_t body_size, const char *content_type) { char body[body_size + 1]; char *body_ptr; @@ -437,8 +529,28 @@ static int camqp_read_body (camqp_config_t *conf, /* {{{ */ received += frame.payload.body_fragment.len; } /* while (received < body_size) */ - DEBUG ("amqp plugin: camqp_read_body: body = %s", body); + if (strcasecmp ("text/collectd", content_type) == 0) + { + status = handle_putval (stderr, body); + if (status != 0) + ERROR ("amqp plugin: handle_putval failed with status %i.", + status); + return (status); + } + else if (strcasecmp ("application/json", content_type) == 0) + { + ERROR ("amqp plugin: camqp_read_body: Parsing JSON data has not " + "been implemented yet. FIXME!"); + return (0); + } + else + { + ERROR ("amqp plugin: camqp_read_body: Unknown content type \"%s\".", + content_type); + return (EINVAL); + } + /* not reached */ return (0); } /* }}} int camqp_read_body */ @@ -446,6 +558,8 @@ static int camqp_read_header (camqp_config_t *conf) /* {{{ */ { int status; amqp_frame_t frame; + amqp_basic_properties_t *properties; + char *content_type; status = amqp_simple_wait_frame (conf->connection, &frame); if (status < 0) @@ -465,7 +579,20 @@ static int camqp_read_header (camqp_config_t *conf) /* {{{ */ return (-1); } - return (camqp_read_body (conf, frame.payload.properties.body_size)); + properties = frame.payload.properties.decoded; + content_type = camqp_bytes_cstring (&properties->content_type); + if (content_type == NULL) + { + ERROR ("amqp plugin: Unable to determine content type."); + return (-1); + } + + status = camqp_read_body (conf, + (size_t) frame.payload.properties.body_size, + content_type); + + sfree (content_type); + return (status); } /* }}} int camqp_read_header */ static void *camqp_subscribe_thread (void *user_data) /* {{{ */ @@ -480,19 +607,25 @@ static void *camqp_subscribe_thread (void *user_data) /* {{{ */ status = camqp_connect (conf); if (status != 0) { + struct timespec ts_interval; ERROR ("amqp plugin: camqp_connect failed. " - "Will sleep for %i seconds.", interval_g); - sleep (interval_g); + "Will sleep for %.3f seconds.", + CDTIME_T_TO_DOUBLE (interval_g)); + CDTIME_T_TO_TIMESPEC (interval_g, &ts_interval); + nanosleep (&ts_interval, /* remaining = */ NULL); continue; } status = amqp_simple_wait_frame (conf->connection, &frame); if (status < 0) { + struct timespec ts_interval; ERROR ("amqp plugin: amqp_simple_wait_frame failed. " - "Will sleep for %i seconds.", interval_g); + "Will sleep for %.3f seconds.", + CDTIME_T_TO_DOUBLE (interval_g)); camqp_close_connection (conf); - sleep (interval_g); + CDTIME_T_TO_TIMESPEC (interval_g, &ts_interval); + nanosleep (&ts_interval, /* remaining = */ NULL); continue; } @@ -517,6 +650,7 @@ static void *camqp_subscribe_thread (void *user_data) /* {{{ */ camqp_config_free (conf); pthread_exit (NULL); + return (NULL); } /* }}} void *camqp_subscribe_thread */ static int camqp_subscribe_init (camqp_config_t *conf) /* {{{ */ @@ -552,8 +686,12 @@ static int camqp_subscribe_init (camqp_config_t *conf) /* {{{ */ return (0); } /* }}} int camqp_subscribe_init */ +/* + * Publishing code + */ +/* XXX: You must hold "conf->lock" when calling this function! */ static int camqp_write_locked (camqp_config_t *conf, /* {{{ */ - const char *buffer) + const char *buffer, const char *routing_key) { amqp_basic_properties_t props; int status; @@ -566,14 +704,21 @@ static int camqp_write_locked (camqp_config_t *conf, /* {{{ */ props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_APP_ID_FLAG; - props.content_type = amqp_cstring_bytes("application/json"); + if (conf->format == CAMQP_FORMAT_COMMAND) + props.content_type = amqp_cstring_bytes("text/collectd"); + else if (conf->format == CAMQP_FORMAT_JSON) + props.content_type = amqp_cstring_bytes("application/json"); + else if (conf->format == CAMQP_FORMAT_GRAPHITE) + props.content_type = amqp_cstring_bytes("text/graphite"); + else + assert (23 == 42); props.delivery_mode = conf->delivery_mode; props.app_id = amqp_cstring_bytes("collectd"); status = amqp_basic_publish(conf->connection, /* channel = */ 1, amqp_cstring_bytes(CONF(conf, exchange)), - amqp_cstring_bytes(CONF(conf, routingkey)), + amqp_cstring_bytes (routing_key), /* mandatory = */ 0, /* immediate = */ 0, &props, @@ -592,29 +737,114 @@ static int camqp_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */ user_data_t *user_data) { camqp_config_t *conf = user_data->data; + char routing_key[6 * DATA_MAX_NAME_LEN]; char buffer[4096]; - size_t bfree; - size_t bfill; int status; if ((ds == NULL) || (vl == NULL) || (conf == NULL)) return (EINVAL); memset (buffer, 0, sizeof (buffer)); - bfree = sizeof (buffer); - bfill = 0; - format_json_initialize (buffer, &bfill, &bfree); - format_json_value_list (buffer, &bfill, &bfree, ds, vl, conf->store_rates); - format_json_finalize (buffer, &bfill, &bfree); + if (conf->routing_key != NULL) + { + sstrncpy (routing_key, conf->routing_key, sizeof (routing_key)); + } + else + { + size_t i; + ssnprintf (routing_key, sizeof (routing_key), "collectd/%s/%s/%s/%s/%s", + vl->host, + vl->plugin, vl->plugin_instance, + vl->type, vl->type_instance); + + /* Switch slashes (the only character forbidden by collectd) and dots + * (the separation character used by AMQP). */ + for (i = 0; routing_key[i] != 0; i++) + { + if (routing_key[i] == '.') + routing_key[i] = '/'; + else if (routing_key[i] == '/') + routing_key[i] = '.'; + } + } + + if (conf->format == CAMQP_FORMAT_COMMAND) + { + status = create_putval (buffer, sizeof (buffer), ds, vl); + if (status != 0) + { + ERROR ("amqp plugin: create_putval failed with status %i.", + status); + return (status); + } + } + else if (conf->format == CAMQP_FORMAT_JSON) + { + size_t bfree = sizeof (buffer); + size_t bfill = 0; + + format_json_initialize (buffer, &bfill, &bfree); + format_json_value_list (buffer, &bfill, &bfree, ds, vl, conf->store_rates); + format_json_finalize (buffer, &bfill, &bfree); + } + else if (conf->format == CAMQP_FORMAT_GRAPHITE) + { + status = format_graphite (buffer, sizeof (buffer), ds, vl, + conf->prefix, conf->postfix, conf->escape_char, + conf->store_rates); + if (status != 0) + { + ERROR ("amqp plugin: format_graphite failed with status %i.", + status); + return (status); + } + } + else + { + ERROR ("amqp plugin: Invalid format (%i).", conf->format); + return (-1); + } pthread_mutex_lock (&conf->lock); - status = camqp_write_locked (conf, buffer); + status = camqp_write_locked (conf, buffer, routing_key); pthread_mutex_unlock (&conf->lock); return (status); } /* }}} int camqp_write */ +/* + * Config handling + */ +static int camqp_config_set_format (oconfig_item_t *ci, /* {{{ */ + camqp_config_t *conf) +{ + char *string; + int status; + + string = NULL; + status = cf_util_get_string (ci, &string); + if (status != 0) + return (status); + + assert (string != NULL); + if (strcasecmp ("Command", string) == 0) + conf->format = CAMQP_FORMAT_COMMAND; + else if (strcasecmp ("JSON", string) == 0) + conf->format = CAMQP_FORMAT_JSON; + else if (strcasecmp ("Graphite", string) == 0) + conf->format = CAMQP_FORMAT_GRAPHITE; + else + { + WARNING ("amqp plugin: Invalid format string: %s", + string); + } + + free (string); + + return (0); +} /* }}} int config_set_string */ + static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */ _Bool publish) { @@ -633,16 +863,21 @@ static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */ memset (conf, 0, sizeof (*conf)); conf->publish = publish; conf->name = NULL; + conf->format = CAMQP_FORMAT_COMMAND; conf->host = NULL; conf->port = 5672; conf->vhost = NULL; conf->user = NULL; conf->password = NULL; conf->exchange = NULL; - conf->routingkey = NULL; + conf->routing_key = NULL; /* publish only */ conf->delivery_mode = CAMQP_DM_VOLATILE; conf->store_rates = 0; + /* publish & graphite only */ + conf->prefix = NULL; + conf->postfix = NULL; + conf->escape_char = '_'; /* subscribe only */ conf->exchange_type = NULL; conf->queue = NULL; @@ -686,7 +921,7 @@ static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */ else if ((strcasecmp ("Queue", child->key) == 0) && !publish) status = cf_util_get_string (child, &conf->queue); else if (strcasecmp ("RoutingKey", child->key) == 0) - status = cf_util_get_string (child, &conf->routingkey); + status = cf_util_get_string (child, &conf->routing_key); else if ((strcasecmp ("Persistent", child->key) == 0) && publish) { _Bool tmp = 0; @@ -698,6 +933,22 @@ static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */ } else if ((strcasecmp ("StoreRates", child->key) == 0) && publish) status = cf_util_get_boolean (child, &conf->store_rates); + else if ((strcasecmp ("Format", child->key) == 0) && publish) + status = camqp_config_set_format (child, conf); + else if ((strcasecmp ("GraphitePrefix", child->key) == 0) && publish) + status = cf_util_get_string (child, &conf->prefix); + else if ((strcasecmp ("GraphitePostfix", child->key) == 0) && publish) + status = cf_util_get_string (child, &conf->postfix); + else if ((strcasecmp ("GraphiteEscapeChar", child->key) == 0) && publish) + { + char *tmp_buff = NULL; + status = cf_util_get_string (child, &tmp_buff); + if (strlen (tmp_buff) > 1) + WARNING ("amqp plugin: The option \"GraphiteEscapeChar\" handles " + "only one character. Others will be ignored."); + conf->escape_char = tmp_buff[0]; + sfree (tmp_buff); + } else WARNING ("amqp plugin: Ignoring unknown " "configuration option \"%s\".", child->key); @@ -706,15 +957,16 @@ static int camqp_config_connection (oconfig_item_t *ci, /* {{{ */ break; } /* for (i = 0; i < ci->children_num; i++) */ - if ((status == 0) && !publish && (conf->exchange == NULL)) + if ((status == 0) && (conf->exchange == NULL)) { - if (conf->routingkey != NULL) - WARNING ("amqp plugin: The option \"RoutingKey\" was given " - "without the \"Exchange\" option. It will be ignored."); - if (conf->exchange_type != NULL) WARNING ("amqp plugin: The option \"ExchangeType\" was given " "without the \"Exchange\" option. It will be ignored."); + + if (!publish && (conf->routing_key != NULL)) + WARNING ("amqp plugin: The option \"RoutingKey\" was given " + "without the \"Exchange\" option. It will be ignored."); + } if (status != 0) @@ -776,35 +1028,10 @@ static int camqp_config (oconfig_item_t *ci) /* {{{ */ return (0); } /* }}} int camqp_config */ -static int shutdown (void) /* {{{ */ -{ - size_t i; - - DEBUG ("amqp plugin: Shutting down %zu subscriber threads.", - subscriber_threads_num); - - subscriber_threads_running = 0; - for (i = 0; i < subscriber_threads_num; i++) - { - /* FIXME: Sending a signal is not very elegant here. Maybe find out how - * to use a timeout in the thread and check for the variable in regular - * intervals. */ - pthread_kill (subscriber_threads[i], SIGTERM); - pthread_join (subscriber_threads[i], /* retval = */ NULL); - } - - subscriber_threads_num = 0; - sfree (subscriber_threads); - - DEBUG ("amqp plugin: All subscriber threads exited."); - - return (0); -} /* }}} int shutdown */ - void module_register (void) { plugin_register_complex_config ("amqp", camqp_config); - plugin_register_shutdown ("amqp", shutdown); + plugin_register_shutdown ("amqp", camqp_shutdown); } /* void module_register */ /* vim: set sw=4 sts=4 et fdm=marker : */