diff --git a/src/amqp.c b/src/amqp.c
index c301dca89706e3d6337e54ed0bf5313faccfd3a8..aba4f0122ca7c87ab4313da6cc3fd318872a9d88 100644 (file)
--- a/src/amqp.c
+++ b/src/amqp.c
/**
* collectd - src/amqp.c
/**
* 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"),
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* Authors:
* Sebastien Pahl <sebastien.pahl at dotcloud.com>
*
* Authors:
* Sebastien Pahl <sebastien.pahl at dotcloud.com>
- * Florian Forster <octo at verplant.org>
+ * Florian Forster <octo at collectd.org>
**/
#include "collectd.h"
**/
#include "collectd.h"
#include "plugin.h"
#include "utils_cmd_putval.h"
#include "utils_format_json.h"
#include "plugin.h"
#include "utils_cmd_putval.h"
#include "utils_format_json.h"
+#include "utils_format_graphite.h"
#include <pthread.h>
#include <amqp.h>
#include <amqp_framing.h>
#include <pthread.h>
#include <amqp.h>
#include <amqp_framing.h>
+#ifdef HAVE_AMQP_TCP_SOCKET_H
+# include <amqp_tcp_socket.h>
+#endif
+#ifdef HAVE_AMQP_SOCKET_H
+# include <amqp_socket.h>
+#endif
+#ifdef HAVE_AMQP_TCP_SOCKET
+#if defined HAVE_DECL_AMQP_SOCKET_CLOSE && !HAVE_DECL_AMQP_SOCKET_CLOSE
+/* rabbitmq-c does not currently ship amqp_socket.h
+ * and, thus, does not define this function. */
+int amqp_socket_close(amqp_socket_t *);
+#endif
+#endif
+
/* Defines for the delivery mode. I have no idea why they're not defined by the
* library.. */
#define CAMQP_DM_VOLATILE 1
#define CAMQP_DM_PERSISTENT 2
/* Defines for the delivery mode. I have no idea why they're not defined by the
* library.. */
#define CAMQP_DM_VOLATILE 1
#define CAMQP_DM_PERSISTENT 2
-#define CAMQP_FORMAT_COMMAND 1
-#define CAMQP_FORMAT_JSON 2
+#define CAMQP_FORMAT_COMMAND 1
+#define CAMQP_FORMAT_JSON 2
+#define CAMQP_FORMAT_GRAPHITE 3
#define CAMQP_CHANNEL 1
#define CAMQP_CHANNEL 1
char *exchange;
char *routing_key;
char *exchange;
char *routing_key;
+ /* Number of seconds to wait before connection is retried */
+ int connection_retry_delay;
+
/* publish only */
uint8_t delivery_mode;
_Bool store_rates;
int format;
/* publish only */
uint8_t delivery_mode;
_Bool store_rates;
int format;
+ /* publish & graphite format only */
+ char *prefix;
+ char *postfix;
+ char escape_char;
+ unsigned int graphite_flags;
/* subscribe only */
char *exchange_type;
char *queue;
/* subscribe only */
char *exchange_type;
char *queue;
+ _Bool queue_durable;
+ _Bool queue_auto_delete;
amqp_connection_state_t connection;
pthread_mutex_t lock;
amqp_connection_state_t connection;
pthread_mutex_t lock;
sfree (conf->exchange_type);
sfree (conf->queue);
sfree (conf->routing_key);
sfree (conf->exchange_type);
sfree (conf->queue);
sfree (conf->routing_key);
+ sfree (conf->prefix);
+ sfree (conf->postfix);
+
sfree (conf);
} /* }}} void camqp_config_free */
sfree (conf);
} /* }}} void camqp_config_free */
break;
case AMQP_RESPONSE_LIBRARY_EXCEPTION:
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));
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;
else
sstrncpy (buffer, "End of stream", sizeof (buffer));
break;
return (buffer);
} /* }}} char *camqp_strerror */
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;
static int camqp_create_exchange (camqp_config_t *conf) /* {{{ */
{
amqp_exchange_declare_ok_t *ed_ret;
return (0);
} /* }}} int camqp_create_exchange */
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,
+#if defined(AMQP_VERSION) && AMQP_VERSION >= 0x00060000
+ /* auto delete = */ 0,
+ /* internal = */ 0,
+#endif
+ /* 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) /* {{{ */
{
static int camqp_setup_queue (camqp_config_t *conf) /* {{{ */
{
? amqp_cstring_bytes (conf->queue)
: AMQP_EMPTY_BYTES,
/* passive = */ 0,
? amqp_cstring_bytes (conf->queue)
: AMQP_EMPTY_BYTES,
/* passive = */ 0,
- /* durable = */ 0,
+ /* durable = */ conf->queue_durable,
/* exclusive = */ 0,
/* exclusive = */ 0,
- /* auto_delete = */ 1,
+ /* auto_delete = */ conf->queue_auto_delete,
/* arguments = */ AMQP_EMPTY_TABLE);
if (qd_ret == NULL)
{
/* arguments = */ AMQP_EMPTY_TABLE);
if (qd_ret == NULL)
{
/* consumer_tag = */ AMQP_EMPTY_BYTES,
/* no_local = */ 0,
/* no_ack = */ 1,
/* 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];
if ((cm_ret == NULL) && camqp_is_error (conf))
{
char errbuf[1024];
static int camqp_connect (camqp_config_t *conf) /* {{{ */
{
static int camqp_connect (camqp_config_t *conf) /* {{{ */
{
+ static time_t last_connect_time = 0;
+
amqp_rpc_reply_t reply;
amqp_rpc_reply_t reply;
- int sockfd;
int status;
int status;
+#ifdef HAVE_AMQP_TCP_SOCKET
+ amqp_socket_t *socket;
+#else
+ int sockfd;
+#endif
if (conf->connection != NULL)
return (0);
if (conf->connection != NULL)
return (0);
+ time_t now = time(NULL);
+ if (now < (last_connect_time + conf->connection_retry_delay))
+ {
+ DEBUG("amqp plugin: skipping connection retry, "
+ "ConnectionRetryDelay: %d", conf->connection_retry_delay);
+ return(1);
+ }
+ else
+ {
+ DEBUG ("amqp plugin: retrying connection");
+ last_connect_time = now;
+ }
+
conf->connection = amqp_new_connection ();
if (conf->connection == NULL)
{
conf->connection = amqp_new_connection ();
if (conf->connection == NULL)
{
return (ENOMEM);
}
return (ENOMEM);
}
+#ifdef HAVE_AMQP_TCP_SOCKET
+# define CLOSE_SOCKET() /* amqp_destroy_connection() closes the socket for us */
+ /* TODO: add support for SSL using amqp_ssl_socket_new
+ * and related functions */
+ socket = amqp_tcp_socket_new (conf->connection);
+ if (! socket)
+ {
+ ERROR ("amqp plugin: amqp_tcp_socket_new failed.");
+ amqp_destroy_connection (conf->connection);
+ conf->connection = NULL;
+ return (ENOMEM);
+ }
+
+ status = amqp_socket_open (socket, CONF(conf, host), conf->port);
+ if (status < 0)
+ {
+ char errbuf[1024];
+ status *= -1;
+ ERROR ("amqp plugin: amqp_socket_open failed: %s",
+ sstrerror (status, errbuf, sizeof (errbuf)));
+ amqp_destroy_connection (conf->connection);
+ conf->connection = NULL;
+ return (status);
+ }
+#else /* HAVE_AMQP_TCP_SOCKET */
+# define CLOSE_SOCKET() close(sockfd)
+ /* this interface is deprecated as of rabbitmq-c 0.4 */
sockfd = amqp_open_socket (CONF(conf, host), conf->port);
if (sockfd < 0)
{
sockfd = amqp_open_socket (CONF(conf, host), conf->port);
if (sockfd < 0)
{
return (status);
}
amqp_set_sockfd (conf->connection, sockfd);
return (status);
}
amqp_set_sockfd (conf->connection, sockfd);
+#endif
reply = amqp_login (conf->connection, CONF(conf, vhost),
/* channel max = */ 0,
reply = amqp_login (conf->connection, CONF(conf, vhost),
/* channel max = */ 0,
ERROR ("amqp plugin: amqp_login (vhost = %s, user = %s) failed.",
CONF(conf, vhost), CONF(conf, user));
amqp_destroy_connection (conf->connection);
ERROR ("amqp plugin: amqp_login (vhost = %s, user = %s) failed.",
CONF(conf, vhost), CONF(conf, user));
amqp_destroy_connection (conf->connection);
- close (sockfd);
+ CLOSE_SOCKET ();
conf->connection = NULL;
return (1);
}
conf->connection = NULL;
return (1);
}
ERROR ("amqp plugin: amqp_channel_open failed.");
amqp_connection_close (conf->connection, AMQP_REPLY_SUCCESS);
amqp_destroy_connection (conf->connection);
ERROR ("amqp plugin: amqp_channel_open failed.");
amqp_connection_close (conf->connection, AMQP_REPLY_SUCCESS);
amqp_destroy_connection (conf->connection);
- close(sockfd);
+ CLOSE_SOCKET ();
conf->connection = NULL;
return (1);
}
conf->connection = NULL;
return (1);
}
camqp_config_t *conf = user_data;
int status;
camqp_config_t *conf = user_data;
int status;
+ cdtime_t interval = plugin_get_interval ();
+
while (subscriber_threads_running)
{
amqp_frame_t frame;
while (subscriber_threads_running)
{
amqp_frame_t frame;
status = camqp_connect (conf);
if (status != 0)
{
status = camqp_connect (conf);
if (status != 0)
{
+ struct timespec ts_interval;
ERROR ("amqp plugin: camqp_connect failed. "
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));
+ CDTIME_T_TO_TIMESPEC (interval, &ts_interval);
+ nanosleep (&ts_interval, /* remaining = */ NULL);
continue;
}
status = amqp_simple_wait_frame (conf->connection, &frame);
if (status < 0)
{
continue;
}
status = amqp_simple_wait_frame (conf->connection, &frame);
if (status < 0)
{
+ struct timespec ts_interval;
ERROR ("amqp plugin: amqp_simple_wait_frame failed. "
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));
camqp_close_connection (conf);
camqp_close_connection (conf);
- sleep (interval_g);
+ CDTIME_T_TO_TIMESPEC (interval, &ts_interval);
+ nanosleep (&ts_interval, /* remaining = */ NULL);
continue;
}
continue;
}
continue;
}
continue;
}
- status = camqp_read_header (conf);
+ camqp_read_header (conf);
amqp_maybe_release_buffers (conf->connection);
} /* while (subscriber_threads_running) */
camqp_config_free (conf);
pthread_exit (NULL);
amqp_maybe_release_buffers (conf->connection);
} /* while (subscriber_threads_running) */
camqp_config_free (conf);
pthread_exit (NULL);
+ return (NULL);
} /* }}} void *camqp_subscribe_thread */
static int camqp_subscribe_init (camqp_config_t *conf) /* {{{ */
} /* }}} void *camqp_subscribe_thread */
static int camqp_subscribe_init (camqp_config_t *conf) /* {{{ */
tmp = subscriber_threads + subscriber_threads_num;
memset (tmp, 0, sizeof (*tmp));
tmp = subscriber_threads + subscriber_threads_num;
memset (tmp, 0, sizeof (*tmp));
- status = pthread_create (tmp, /* attr = */ NULL,
+ status = plugin_thread_create (tmp, /* attr = */ NULL,
camqp_subscribe_thread, conf);
if (status != 0)
{
camqp_subscribe_thread, conf);
if (status != 0)
{
/*
* Publishing code
*/
/*
* 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 *routing_key)
{
static int camqp_write_locked (camqp_config_t *conf, /* {{{ */
const char *buffer, const char *routing_key)
{
props.content_type = amqp_cstring_bytes("text/collectd");
else if (conf->format == CAMQP_FORMAT_JSON)
props.content_type = amqp_cstring_bytes("application/json");
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;
else
assert (23 == 42);
props.delivery_mode = conf->delivery_mode;
{
camqp_config_t *conf = user_data->data;
char routing_key[6 * DATA_MAX_NAME_LEN];
{
camqp_config_t *conf = user_data->data;
char routing_key[6 * DATA_MAX_NAME_LEN];
- char buffer[4096];
+ char buffer[8192];
int status;
if ((ds == NULL) || (vl == NULL) || (conf == NULL))
int status;
if ((ds == NULL) || (vl == NULL) || (conf == NULL))
@@ -721,6 +861,18 @@ static int camqp_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
format_json_value_list (buffer, &bfill, &bfree, ds, vl, conf->store_rates);
format_json_finalize (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->graphite_flags);
+ if (status != 0)
+ {
+ ERROR ("amqp plugin: format_graphite failed with status %i.",
+ status);
+ return (status);
+ }
+ }
else
{
ERROR ("amqp plugin: Invalid format (%i).", conf->format);
else
{
ERROR ("amqp plugin: Invalid format (%i).", conf->format);
conf->format = CAMQP_FORMAT_COMMAND;
else if (strcasecmp ("JSON", string) == 0)
conf->format = CAMQP_FORMAT_JSON;
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",
else
{
WARNING ("amqp plugin: Invalid format string: %s",
conf->password = NULL;
conf->exchange = NULL;
conf->routing_key = NULL;
conf->password = NULL;
conf->exchange = NULL;
conf->routing_key = NULL;
+ conf->connection_retry_delay = 0;
+
/* publish only */
conf->delivery_mode = CAMQP_DM_VOLATILE;
conf->store_rates = 0;
/* publish only */
conf->delivery_mode = CAMQP_DM_VOLATILE;
conf->store_rates = 0;
+ conf->graphite_flags = 0;
+ /* publish & graphite only */
+ conf->prefix = NULL;
+ conf->postfix = NULL;
+ conf->escape_char = '_';
/* subscribe only */
conf->exchange_type = NULL;
conf->queue = NULL;
/* subscribe only */
conf->exchange_type = NULL;
conf->queue = NULL;
+ conf->queue_durable = 0;
+ conf->queue_auto_delete = 1;
/* general */
conf->connection = NULL;
pthread_mutex_init (&conf->lock, /* attr = */ NULL);
/* general */
conf->connection = NULL;
pthread_mutex_init (&conf->lock, /* attr = */ NULL);
status = cf_util_get_string (child, &conf->exchange_type);
else if ((strcasecmp ("Queue", child->key) == 0) && !publish)
status = cf_util_get_string (child, &conf->queue);
status = cf_util_get_string (child, &conf->exchange_type);
else if ((strcasecmp ("Queue", child->key) == 0) && !publish)
status = cf_util_get_string (child, &conf->queue);
+ else if ((strcasecmp ("QueueDurable", child->key) == 0) && !publish)
+ status = cf_util_get_boolean (child, &conf->queue_durable);
+ else if ((strcasecmp ("QueueAutoDelete", child->key) == 0) && !publish)
+ status = cf_util_get_boolean (child, &conf->queue_auto_delete);
else if (strcasecmp ("RoutingKey", child->key) == 0)
status = cf_util_get_string (child, &conf->routing_key);
else if ((strcasecmp ("Persistent", child->key) == 0) && publish)
else if (strcasecmp ("RoutingKey", child->key) == 0)
status = cf_util_get_string (child, &conf->routing_key);
else if ((strcasecmp ("Persistent", child->key) == 0) && publish)
conf->delivery_mode = CAMQP_DM_VOLATILE;
}
else if ((strcasecmp ("StoreRates", child->key) == 0) && publish)
conf->delivery_mode = CAMQP_DM_VOLATILE;
}
else if ((strcasecmp ("StoreRates", child->key) == 0) && publish)
+ {
status = cf_util_get_boolean (child, &conf->store_rates);
status = cf_util_get_boolean (child, &conf->store_rates);
+ (void) cf_util_get_flag (child, &conf->graphite_flags,
+ GRAPHITE_STORE_RATES);
+ }
else if ((strcasecmp ("Format", child->key) == 0) && publish)
status = camqp_config_set_format (child, conf);
else if ((strcasecmp ("Format", child->key) == 0) && publish)
status = camqp_config_set_format (child, conf);
+ else if ((strcasecmp ("GraphiteSeparateInstances", child->key) == 0) && publish)
+ status = cf_util_get_flag (child, &conf->graphite_flags,
+ GRAPHITE_SEPARATE_INSTANCES);
+ else if ((strcasecmp ("GraphiteAlwaysAppendDS", child->key) == 0) && publish)
+ status = cf_util_get_flag (child, &conf->graphite_flags,
+ GRAPHITE_ALWAYS_APPEND_DS);
+ 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 if (strcasecmp ("ConnectionRetryDelay", child->key) == 0)
+ status = cf_util_get_int (child, &conf->connection_retry_delay);
else
WARNING ("amqp plugin: Ignoring unknown "
"configuration option \"%s\".", child->key);
else
WARNING ("amqp plugin: Ignoring unknown "
"configuration option \"%s\".", child->key);