|
@@ -0,0 +1,1147 @@
|
|
|
|
+/*
|
|
|
|
+ * Copyright (C) 2019 Vicente Hernando (Sonoc https://www.sonoc.io)
|
|
|
|
+ *
|
|
|
|
+ * This file is part of Kamailio, a free SIP server.
|
|
|
|
+ *
|
|
|
|
+ * Kamailio is free software; you can redistribute it and/or modify
|
|
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
|
|
+ * (at your option) any later version
|
|
|
|
+ *
|
|
|
|
+ * Kamailio is distributed in the hope that it will be useful,
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
+ * GNU General Public License for more details.
|
|
|
|
+ *
|
|
|
|
+ * You should have received a copy of the GNU General Public License
|
|
|
|
+ * along with this program; if not, write to the Free Software
|
|
|
|
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \file
|
|
|
|
+ * \brief Kafka :: Apache Kafka functions via librdkafka
|
|
|
|
+ * \ingroup kfk
|
|
|
|
+ *
|
|
|
|
+ * - Module: \ref kfk
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <syslog.h> /* For log levels. */
|
|
|
|
+#include <librdkafka/rdkafka.h>
|
|
|
|
+
|
|
|
|
+#include "../../core/dprint.h"
|
|
|
|
+#include "../../core/parser/parse_param.h"
|
|
|
|
+#include "../../core/mem/pkg.h"
|
|
|
|
+#include "../../core/mem/shm_mem.h"
|
|
|
|
+#include "../../core/locking.h"
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief data type for a configuration property.
|
|
|
|
+ */
|
|
|
|
+typedef struct kfk_conf_node_s {
|
|
|
|
+ str *sname; /**< name of property */
|
|
|
|
+ str *svalue; /**< value of property */
|
|
|
|
+ struct kfk_conf_node_s *next; /**< next property in list */
|
|
|
|
+} kfk_conf_node_t;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief list of configuration properties.
|
|
|
|
+ */
|
|
|
|
+typedef struct kfk_conf_s {
|
|
|
|
+ param_t *attrs; /**< parsed attributes from configuration parameter. */
|
|
|
|
+ char *spec; /**< original string of configuration. */
|
|
|
|
+ kfk_conf_node_t *property; /**< list of configuration properties. */
|
|
|
|
+} kfk_conf_t;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief data type for a topic.
|
|
|
|
+ *
|
|
|
|
+ * This is an element in a topic list.
|
|
|
|
+ */
|
|
|
|
+typedef struct kfk_topic_s {
|
|
|
|
+ str *topic_name; /**< Name of the topic. */
|
|
|
|
+ rd_kafka_topic_t *rd_topic; /**< rd kafkfa topic structure. */
|
|
|
|
+ param_t *attrs; /**< parsed attributes for topic configuration. */
|
|
|
|
+ char *spec; /**< original string for topic configuration. */
|
|
|
|
+ kfk_conf_node_t *property; /**< list of configuration properties for a topic. */
|
|
|
|
+ struct kfk_topic_s *next; /**< Next element in topic list. */
|
|
|
|
+} kfk_topic_t;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief stats about a topic.
|
|
|
|
+ */
|
|
|
|
+typedef struct kfk_stats_s {
|
|
|
|
+ str *topic_name; /**< Name of the topic, or NULL for general statistics. */
|
|
|
|
+ uint64_t total; /**< Total number of messages sent. */
|
|
|
|
+ uint64_t error; /**< Number of failed messages to sent. */
|
|
|
|
+ struct kfk_stats_s *next; /**< Next element in stats list. */
|
|
|
|
+} kfk_stats_t;
|
|
|
|
+
|
|
|
|
+/* Static variables. */
|
|
|
|
+static rd_kafka_conf_t *rk_conf = NULL; /* Configuration object */
|
|
|
|
+static rd_kafka_t *rk = NULL; /* Producer instance handle */
|
|
|
|
+static kfk_conf_t *kfk_conf = NULL; /* List for Kafka configuration properties. */
|
|
|
|
+static kfk_topic_t *kfk_topic = NULL; /* List for Kafka topics. */
|
|
|
|
+
|
|
|
|
+#define ERRSTR_LEN 512 /**< Length of internal buffer for errors. */
|
|
|
|
+static char errstr[ERRSTR_LEN]; /* librdkafka API error reporting buffer */
|
|
|
|
+gen_lock_t *stats_lock = NULL; /**< Lock to protect shared statistics data. */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Total statistics
|
|
|
|
+ *
|
|
|
|
+ * First node (mandatory) is the general one with NULL topic.
|
|
|
|
+ * Next nodes are topic dependant ones and are optional.
|
|
|
|
+ * This way because general node is created in kfk_stats_init in mod_init is
|
|
|
|
+ * shared among every Kamailio process.
|
|
|
|
+ */
|
|
|
|
+static kfk_stats_t *stats_general;
|
|
|
|
+
|
|
|
|
+/* Static functions. */
|
|
|
|
+static void kfk_conf_free(kfk_conf_t *kconf);
|
|
|
|
+static void kfk_topic_free(kfk_topic_t *ktopic);
|
|
|
|
+static int kfk_conf_configure();
|
|
|
|
+static int kfk_topic_list_configure();
|
|
|
|
+static int kfk_topic_exist(str *topic_name);
|
|
|
|
+static rd_kafka_topic_t* kfk_topic_get(str *topic_name);
|
|
|
|
+static int kfk_stats_add(const char *topic, rd_kafka_resp_err_t err);
|
|
|
|
+static void kfk_stats_topic_free(kfk_stats_t *st_topic);
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Kafka logger callback
|
|
|
|
+ */
|
|
|
|
+static void kfk_logger (const rd_kafka_t *rk, int level,
|
|
|
|
+ const char *fac, const char *buf) {
|
|
|
|
+
|
|
|
|
+ switch(level) {
|
|
|
|
+ case LOG_EMERG:
|
|
|
|
+ LM_NPRL("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_ALERT:
|
|
|
|
+ LM_ALERT("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_CRIT:
|
|
|
|
+ LM_CRIT("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_ERR:
|
|
|
|
+ LM_ERR("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_WARNING:
|
|
|
|
+ LM_WARN("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_NOTICE:
|
|
|
|
+ LM_NOTICE("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_INFO:
|
|
|
|
+ LM_INFO("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case LOG_DEBUG:
|
|
|
|
+ LM_DBG("RDKAFKA fac: %s : %s : %s\n",
|
|
|
|
+ fac, rk ? rd_kafka_name(rk) : NULL,
|
|
|
|
+ buf);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ LM_ERR("Unsupported kafka log level: %d\n", level);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Message delivery report callback using the richer rd_kafka_message_t object.
|
|
|
|
+ */
|
|
|
|
+static void kfk_msg_delivered (rd_kafka_t *rk,
|
|
|
|
+ const rd_kafka_message_t *rkmessage, void *opaque) {
|
|
|
|
+
|
|
|
|
+ LM_DBG("Message delivered callback\n");
|
|
|
|
+
|
|
|
|
+ const char *topic_name = NULL;
|
|
|
|
+ topic_name = rd_kafka_topic_name(rkmessage->rkt);
|
|
|
|
+ if (!topic_name) {
|
|
|
|
+ LM_ERR("Cannot get topic name for delivered message\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kfk_stats_add(topic_name, rkmessage->err);
|
|
|
|
+
|
|
|
|
+ if (rkmessage->err) {
|
|
|
|
+ LM_ERR("RDKAFKA Message delivery failed: %s\n",
|
|
|
|
+ rd_kafka_err2str(rkmessage->err));
|
|
|
|
+ } else {
|
|
|
|
+ LM_DBG("RDKAFKA Message delivered (%zd bytes, offset %"PRId64", "
|
|
|
|
+ "partition %"PRId32"): %.*s\n",
|
|
|
|
+ rkmessage->len, rkmessage->offset,
|
|
|
|
+ rkmessage->partition,
|
|
|
|
+ (int)rkmessage->len, (const char *)rkmessage->payload);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Initialize kafka functionality.
|
|
|
|
+ *
|
|
|
|
+ * \param brokers brokers to add.
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+int kfk_init(char *brokers)
|
|
|
|
+{
|
|
|
|
+ LM_DBG("Initializing Kafka\n");
|
|
|
|
+
|
|
|
|
+ if (brokers == NULL) {
|
|
|
|
+ LM_ERR("brokers parameter not set\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Create Kafka client configuration place-holder
|
|
|
|
+ */
|
|
|
|
+ rk_conf = rd_kafka_conf_new();
|
|
|
|
+
|
|
|
|
+ /* Set logger */
|
|
|
|
+ rd_kafka_conf_set_log_cb(rk_conf, kfk_logger);
|
|
|
|
+
|
|
|
|
+ /* Set message delivery callback. */
|
|
|
|
+ rd_kafka_conf_set_dr_msg_cb(rk_conf, kfk_msg_delivered);
|
|
|
|
+
|
|
|
|
+ /* Configure properties: */
|
|
|
|
+ if (kfk_conf_configure()) {
|
|
|
|
+ LM_ERR("Failed to configure general properties\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Create producer instance.
|
|
|
|
+ *
|
|
|
|
+ * NOTE: rd_kafka_new() takes ownership of the conf object
|
|
|
|
+ * and the application must not reference it again after
|
|
|
|
+ * this call.
|
|
|
|
+ */
|
|
|
|
+ rk = rd_kafka_new(RD_KAFKA_PRODUCER, rk_conf, errstr, sizeof(errstr));
|
|
|
|
+ if (!rk) {
|
|
|
|
+ LM_ERR("Failed to create new producer: %s\n", errstr);
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ rk_conf = NULL; /* Now owned by producer. */
|
|
|
|
+ LM_DBG("Producer handle created\n");
|
|
|
|
+
|
|
|
|
+ LM_DBG("Adding broker: %s\n", brokers);
|
|
|
|
+ /* Add brokers */
|
|
|
|
+ if (rd_kafka_brokers_add(rk, brokers) == 0) {
|
|
|
|
+ LM_ERR("No valid brokers specified: %s\n", brokers);
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ LM_DBG("Added broker: %s\n", brokers);
|
|
|
|
+
|
|
|
|
+ /* Topic creation and configuration. */
|
|
|
|
+ if (kfk_topic_list_configure()) {
|
|
|
|
+ LM_ERR("Failed to configure topics\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Close kafka related functionality.
|
|
|
|
+ */
|
|
|
|
+void kfk_close()
|
|
|
|
+{
|
|
|
|
+ rd_kafka_resp_err_t err;
|
|
|
|
+
|
|
|
|
+ LM_DBG("Closing Kafka\n");
|
|
|
|
+
|
|
|
|
+ /* Destroy the producer instance */
|
|
|
|
+ if (rk) {
|
|
|
|
+ /* Flushing messages. */
|
|
|
|
+ LM_DBG("Flushing messages\n");
|
|
|
|
+ err = rd_kafka_flush(rk, 0);
|
|
|
|
+ if (err) {
|
|
|
|
+ LM_ERR("Failed to flush messages: %s\n", rd_kafka_err2str(err));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Destroy producer. */
|
|
|
|
+ LM_DBG("Destroying instance of Kafka producer\n");
|
|
|
|
+ rd_kafka_destroy(rk);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Destroy configuration if not freed by rd_kafka_destroy. */
|
|
|
|
+ if (rk_conf) {
|
|
|
|
+ LM_DBG("Destroying instance of Kafka configuration\n");
|
|
|
|
+ rd_kafka_conf_destroy(rk_conf);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Free list of configuration properties. */
|
|
|
|
+ if (kfk_conf) {
|
|
|
|
+ kfk_conf_free(kfk_conf);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Free list of topics. */
|
|
|
|
+ while (kfk_topic) {
|
|
|
|
+ kfk_topic_t *next = kfk_topic->next;
|
|
|
|
+ kfk_topic_free(kfk_topic);
|
|
|
|
+ kfk_topic = next;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Free a general configuration object.
|
|
|
|
+ */
|
|
|
|
+static void kfk_conf_free(kfk_conf_t *kconf)
|
|
|
|
+{
|
|
|
|
+ if (kconf == NULL) {
|
|
|
|
+ /* Nothing to free. */
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kfk_conf_node_t *knode = kconf->property;
|
|
|
|
+ while (knode) {
|
|
|
|
+ kfk_conf_node_t *next = knode->next;
|
|
|
|
+ pkg_free(knode);
|
|
|
|
+ knode = next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ free_params(kconf->attrs);
|
|
|
|
+ pkg_free(kconf);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Parse general configuration properties for Kafka.
|
|
|
|
+ */
|
|
|
|
+int kfk_conf_parse(char *spec)
|
|
|
|
+{
|
|
|
|
+ param_t *pit = NULL;
|
|
|
|
+ param_hooks_t phooks;
|
|
|
|
+ kfk_conf_t *kconf = NULL;
|
|
|
|
+
|
|
|
|
+ if (kfk_conf != NULL) {
|
|
|
|
+ LM_ERR("Configuration already set\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ str s;
|
|
|
|
+ s.s = spec;
|
|
|
|
+ s.len = strlen(spec);
|
|
|
|
+ if(s.s[s.len-1]==';') {
|
|
|
|
+ s.len--;
|
|
|
|
+ }
|
|
|
|
+ if (parse_params(&s, CLASS_ANY, &phooks, &pit) < 0) {
|
|
|
|
+ LM_ERR("Failed parsing params value\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kconf = (kfk_conf_t*)pkg_malloc(sizeof(kfk_conf_t));
|
|
|
|
+ if (kconf == NULL) {
|
|
|
|
+ LM_ERR("No more pkg memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(kconf, 0, sizeof(kfk_conf_t));
|
|
|
|
+ kconf->attrs = pit;
|
|
|
|
+ kconf->spec = spec;
|
|
|
|
+ for (pit = kconf->attrs; pit; pit=pit->next)
|
|
|
|
+ {
|
|
|
|
+ /* Parse a property. */
|
|
|
|
+ kfk_conf_node_t *knode = NULL;
|
|
|
|
+ knode = (kfk_conf_node_t*)pkg_malloc(sizeof(kfk_conf_node_t));
|
|
|
|
+ if (knode == NULL) {
|
|
|
|
+ LM_ERR("No more pkg memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(knode, 0, sizeof(kfk_conf_node_t));
|
|
|
|
+
|
|
|
|
+ knode->sname = &pit->name;
|
|
|
|
+ knode->svalue = &pit->body;
|
|
|
|
+ if (knode->sname && knode->svalue) {
|
|
|
|
+ LM_DBG("Parsed property: %.*s -> %.*s\n",
|
|
|
|
+ knode->sname->len, knode->sname->s,
|
|
|
|
+ knode->svalue->len, knode->svalue->s);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Place node at beginning of knode list. */
|
|
|
|
+ knode->next = kconf->property;
|
|
|
|
+ kconf->property = knode;
|
|
|
|
+ } /* for pit */
|
|
|
|
+
|
|
|
|
+ kfk_conf = kconf;
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+ if(pit!=NULL) {
|
|
|
|
+ free_params(pit);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(kconf != NULL) {
|
|
|
|
+ kfk_conf_free(kconf);
|
|
|
|
+ }
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Configure Kafka properties.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+static int kfk_conf_configure()
|
|
|
|
+{
|
|
|
|
+ if (kfk_conf == NULL) {
|
|
|
|
+ /* Nothing to configure. */
|
|
|
|
+ LM_DBG("No properties to configure\n");
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("Configuring properties\n");
|
|
|
|
+
|
|
|
|
+ kfk_conf_node_t *knode = kfk_conf->property;
|
|
|
|
+ while (knode) {
|
|
|
|
+ kfk_conf_node_t *next = knode->next;
|
|
|
|
+ str *sname = knode->sname;
|
|
|
|
+ str *svalue = knode->svalue;
|
|
|
|
+ knode = next;
|
|
|
|
+
|
|
|
|
+ if (sname == NULL || sname->len == 0 || sname->s == NULL) {
|
|
|
|
+ LM_ERR("Bad name in configuration property\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (svalue == NULL || svalue->len == 0 || svalue->s == NULL) {
|
|
|
|
+ LM_ERR("Bad value in configuration property\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We temporarily convert to zstring. */
|
|
|
|
+ char cname = sname->s[sname->len];
|
|
|
|
+ sname->s[sname->len] = '\0';
|
|
|
|
+ char cvalue = svalue->s[svalue->len];
|
|
|
|
+ svalue->s[svalue->len] = '\0';
|
|
|
|
+
|
|
|
|
+ LM_DBG("Setting property: %s -> %s\n", sname->s, svalue->s);
|
|
|
|
+
|
|
|
|
+ if (rd_kafka_conf_set(rk_conf, sname->s, svalue->s,
|
|
|
|
+ errstr, sizeof(errstr)) !=
|
|
|
|
+ RD_KAFKA_CONF_OK) {
|
|
|
|
+ LM_ERR("Configuration failed: %s\n", errstr);
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ sname->s[sname->len] = cname;
|
|
|
|
+ svalue->s[svalue->len] = cvalue;
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ sname->s[sname->len] = cname;
|
|
|
|
+ svalue->s[svalue->len] = cvalue;
|
|
|
|
+
|
|
|
|
+ } /* while knode */
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Free a topic object.
|
|
|
|
+ */
|
|
|
|
+static void kfk_topic_free(kfk_topic_t *ktopic)
|
|
|
|
+{
|
|
|
|
+ if (ktopic == NULL) {
|
|
|
|
+ /* Nothing to free. */
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kfk_conf_node_t *knode = ktopic->property;
|
|
|
|
+ while (knode) {
|
|
|
|
+ kfk_conf_node_t *next = knode->next;
|
|
|
|
+ pkg_free(knode);
|
|
|
|
+ knode = next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Destroy rd Kafka topic. */
|
|
|
|
+ if (ktopic->rd_topic) {
|
|
|
|
+ rd_kafka_topic_destroy(ktopic->rd_topic);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ free_params(ktopic->attrs);
|
|
|
|
+ pkg_free(ktopic);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Parse topic properties for Kafka.
|
|
|
|
+ */
|
|
|
|
+int kfk_topic_parse(char *spec)
|
|
|
|
+{
|
|
|
|
+ param_t *pit = NULL;
|
|
|
|
+ param_hooks_t phooks;
|
|
|
|
+ kfk_topic_t *ktopic = NULL;
|
|
|
|
+
|
|
|
|
+ str s;
|
|
|
|
+ s.s = spec;
|
|
|
|
+ s.len = strlen(spec);
|
|
|
|
+ if(s.s[s.len-1]==';') {
|
|
|
|
+ s.len--;
|
|
|
|
+ }
|
|
|
|
+ if (parse_params(&s, CLASS_ANY, &phooks, &pit) < 0) {
|
|
|
|
+ LM_ERR("Failed parsing params value\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ktopic = (kfk_topic_t*)pkg_malloc(sizeof(kfk_topic_t));
|
|
|
|
+ if (ktopic == NULL) {
|
|
|
|
+ LM_ERR("No more pkg memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(ktopic, 0, sizeof(kfk_topic_t));
|
|
|
|
+ ktopic->attrs = pit;
|
|
|
|
+ ktopic->spec = spec;
|
|
|
|
+ for (pit = ktopic->attrs; pit; pit=pit->next)
|
|
|
|
+ {
|
|
|
|
+ /* Check for topic name. */
|
|
|
|
+ if (pit->name.len==4 && strncmp(pit->name.s, "name", 4)==0) {
|
|
|
|
+ if (ktopic->topic_name != NULL) {
|
|
|
|
+ LM_ERR("Topic name already set\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ ktopic->topic_name = &pit->body;
|
|
|
|
+ LM_DBG("Topic name: %.*s\n", pit->body.len, pit->body.s);
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ /* Parse a property. */
|
|
|
|
+ kfk_conf_node_t *knode = NULL;
|
|
|
|
+ knode = (kfk_conf_node_t*)pkg_malloc(sizeof(kfk_conf_node_t));
|
|
|
|
+ if (knode == NULL) {
|
|
|
|
+ LM_ERR("No more pkg memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(knode, 0, sizeof(kfk_conf_node_t));
|
|
|
|
+
|
|
|
|
+ knode->sname = &pit->name;
|
|
|
|
+ knode->svalue = &pit->body;
|
|
|
|
+ if (knode->sname && knode->svalue) {
|
|
|
|
+ LM_DBG("Topic parsed property: %.*s -> %.*s\n",
|
|
|
|
+ knode->sname->len, knode->sname->s,
|
|
|
|
+ knode->svalue->len, knode->svalue->s);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Place node at beginning of ktopic list. */
|
|
|
|
+ knode->next = ktopic->property;
|
|
|
|
+ ktopic->property = knode;
|
|
|
|
+ } /* if pit->name.len == 4 */
|
|
|
|
+ } /* for pit */
|
|
|
|
+
|
|
|
|
+ /* Topic name is mandatory. */
|
|
|
|
+ if(ktopic->topic_name == NULL)
|
|
|
|
+ {
|
|
|
|
+ LM_ERR("No topic name\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Place topic at beginning of topic list. */
|
|
|
|
+ ktopic->next = kfk_topic;
|
|
|
|
+ kfk_topic = ktopic;
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+ if(pit!=NULL) {
|
|
|
|
+ free_params(pit);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(ktopic != NULL) {
|
|
|
|
+ kfk_topic_free(ktopic);
|
|
|
|
+ }
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Create and configure a topic.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+static int kfk_topic_configure(kfk_topic_t *ktopic)
|
|
|
|
+{
|
|
|
|
+ rd_kafka_topic_conf_t *topic_conf = NULL;
|
|
|
|
+ rd_kafka_topic_t *rkt = NULL;
|
|
|
|
+
|
|
|
|
+ if (ktopic == NULL) {
|
|
|
|
+ LM_ERR("No topic to create\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Check topic name. */
|
|
|
|
+ if (!ktopic->topic_name || !ktopic->topic_name->s || ktopic->topic_name->len == 0) {
|
|
|
|
+ LM_ERR("Bad topic name\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int topic_found = kfk_topic_exist(ktopic->topic_name);
|
|
|
|
+ if (topic_found == -1) {
|
|
|
|
+ LM_ERR("Failed to search for topic %.*s in cluster\n",
|
|
|
|
+ ktopic->topic_name->len, ktopic->topic_name->s);
|
|
|
|
+ goto error;
|
|
|
|
+ } else if (topic_found == 0) {
|
|
|
|
+ LM_ERR("Topic not found %.*s in cluster\n",
|
|
|
|
+ ktopic->topic_name->len, ktopic->topic_name->s);
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("Creating topic: %.*s\n",
|
|
|
|
+ ktopic->topic_name->len, ktopic->topic_name->s);
|
|
|
|
+
|
|
|
|
+ /* Topic configuration */
|
|
|
|
+
|
|
|
|
+ topic_conf = rd_kafka_topic_conf_new();
|
|
|
|
+
|
|
|
|
+ kfk_conf_node_t *knode = kfk_topic->property;
|
|
|
|
+ while (knode) {
|
|
|
|
+ kfk_conf_node_t *next = knode->next;
|
|
|
|
+ str *sname = knode->sname;
|
|
|
|
+ str *svalue = knode->svalue;
|
|
|
|
+ knode = next;
|
|
|
|
+
|
|
|
|
+ if (sname == NULL || sname->len == 0 || sname->s == NULL) {
|
|
|
|
+ LM_ERR("Bad name in topic configuration property\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (svalue == NULL || svalue->len == 0 || svalue->s == NULL) {
|
|
|
|
+ LM_ERR("Bad value in topic configuration property\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We temporarily convert to zstring. */
|
|
|
|
+ char cname = sname->s[sname->len];
|
|
|
|
+ sname->s[sname->len] = '\0';
|
|
|
|
+ char cvalue = svalue->s[svalue->len];
|
|
|
|
+ svalue->s[svalue->len] = '\0';
|
|
|
|
+
|
|
|
|
+ LM_DBG("Setting topic property: %s -> %s\n", sname->s, svalue->s);
|
|
|
|
+
|
|
|
|
+ rd_kafka_conf_res_t res;
|
|
|
|
+ res = rd_kafka_topic_conf_set(topic_conf, sname->s, svalue->s,
|
|
|
|
+ errstr, sizeof(errstr));
|
|
|
|
+ if (res != RD_KAFKA_CONF_OK) {
|
|
|
|
+ LM_ERR("Failed to set topic configuration: %s -> %s\n",
|
|
|
|
+ sname->s, svalue->s);
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ sname->s[sname->len] = cname;
|
|
|
|
+ svalue->s[svalue->len] = cvalue;
|
|
|
|
+
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ sname->s[sname->len] = cname;
|
|
|
|
+ svalue->s[svalue->len] = cvalue;
|
|
|
|
+
|
|
|
|
+ } /* while knode */
|
|
|
|
+
|
|
|
|
+ /* We temporarily convert to zstring. */
|
|
|
|
+ char c_topic_name = ktopic->topic_name->s[ktopic->topic_name->len];
|
|
|
|
+ ktopic->topic_name->s[ktopic->topic_name->len] = '\0';
|
|
|
|
+
|
|
|
|
+ rkt = rd_kafka_topic_new(rk, ktopic->topic_name->s, topic_conf);
|
|
|
|
+ if (!rkt) {
|
|
|
|
+ LM_ERR("Failed to create topic (%s): %s\n",
|
|
|
|
+ ktopic->topic_name->s,
|
|
|
|
+ rd_kafka_err2str(rd_kafka_last_error()));
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ ktopic->topic_name->s[ktopic->topic_name->len] = c_topic_name;
|
|
|
|
+
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ topic_conf = NULL; /* Now owned by topic */
|
|
|
|
+ LM_DBG("Topic created: %s\n", ktopic->topic_name->s);
|
|
|
|
+
|
|
|
|
+ /* We restore zstrings back to str */
|
|
|
|
+ ktopic->topic_name->s[ktopic->topic_name->len] = c_topic_name;
|
|
|
|
+
|
|
|
|
+ /* Everything went fine. */
|
|
|
|
+ ktopic->rd_topic = rkt;
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+
|
|
|
|
+ /* Destroy topic configuration. */
|
|
|
|
+ if (topic_conf) {
|
|
|
|
+ rd_kafka_topic_conf_destroy(topic_conf);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Destroy topic */
|
|
|
|
+ if (rkt) {
|
|
|
|
+ LM_DBG("Destroying topic\n");
|
|
|
|
+ rd_kafka_topic_destroy(rkt);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Create and configure a list of topics.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+static int kfk_topic_list_configure()
|
|
|
|
+{
|
|
|
|
+ kfk_topic_t *ktopic = kfk_topic;
|
|
|
|
+
|
|
|
|
+ while (ktopic) {
|
|
|
|
+ kfk_topic_t *next = ktopic->next;
|
|
|
|
+ /* Create current topic. */
|
|
|
|
+ if (kfk_topic_configure(ktopic)) {
|
|
|
|
+ LM_ERR("Failed to create topic: %.*s\n",
|
|
|
|
+ ktopic->topic_name->len, ktopic->topic_name->s);
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ ktopic = next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* -1 means RD_POLL_INFINITE */
|
|
|
|
+/* 100000 means 100 seconds */
|
|
|
|
+#define METADATA_TIMEOUT 100000 /**< Timeout when asking for metadata in milliseconds. */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief check that a topic exists in cluster.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 if topic does not exist.
|
|
|
|
+ * \return 1 if topic does exist.
|
|
|
|
+ * \return -1 on error.
|
|
|
|
+ */
|
|
|
|
+static int kfk_topic_exist(str *topic_name)
|
|
|
|
+{
|
|
|
|
+ /* Where to receive metadata. */
|
|
|
|
+ const struct rd_kafka_metadata *metadatap = NULL;
|
|
|
|
+
|
|
|
|
+ if (!topic_name || topic_name->len == 0 || topic_name->s == NULL) {
|
|
|
|
+ LM_ERR("Bad topic name\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Get metadata for all topics. */
|
|
|
|
+ rd_kafka_resp_err_t res;
|
|
|
|
+ res = rd_kafka_metadata(rk, 1, NULL, &metadatap, METADATA_TIMEOUT);
|
|
|
|
+ if (res != RD_KAFKA_RESP_ERR_NO_ERROR) {
|
|
|
|
+ LM_ERR("Failed to get metadata: %s\n", rd_kafka_err2str(res));
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* List topics */
|
|
|
|
+ int topic_found = 0; /* Topic not found by default. */
|
|
|
|
+
|
|
|
|
+ for (int i=0; i<metadatap->topic_cnt; i++) {
|
|
|
|
+ rd_kafka_metadata_topic_t *t = &metadatap->topics[i];
|
|
|
|
+ if (t->topic) {
|
|
|
|
+ LM_DBG("Metadata Topic: %s\n", t->topic);
|
|
|
|
+ if (strncmp(topic_name->s, t->topic, topic_name->len) == 0) {
|
|
|
|
+ topic_found = 1;
|
|
|
|
+ LM_DBG("Metadata Topic (%s) found!\n", t->topic);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } // for (i=0; i<m->topic_cnt; i++)
|
|
|
|
+
|
|
|
|
+ /* Destroy metadata. */
|
|
|
|
+ rd_kafka_metadata_destroy(metadatap);
|
|
|
|
+
|
|
|
|
+ if (topic_found == 0) {
|
|
|
|
+ LM_DBG("Topic not found: %.*s\n", topic_name->len, topic_name->s);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("Topic found: %.*s\n", topic_name->len, topic_name->s);
|
|
|
|
+ return 1;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+
|
|
|
|
+ /* Destroy metadata. */
|
|
|
|
+ if (metadatap) {
|
|
|
|
+ rd_kafka_metadata_destroy(metadatap);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief get a topic based on its name.
|
|
|
|
+ *
|
|
|
|
+ * \return topic if it founds a matching one, NULL otherwise.
|
|
|
|
+ */
|
|
|
|
+static rd_kafka_topic_t* kfk_topic_get(str *topic_name)
|
|
|
|
+{
|
|
|
|
+ rd_kafka_topic_t *result = NULL;
|
|
|
|
+
|
|
|
|
+ if (!topic_name || topic_name->len == 0 || topic_name->s == NULL) {
|
|
|
|
+ LM_ERR("Bad topic name\n");
|
|
|
|
+ goto clean;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kfk_topic_t *ktopic = kfk_topic;
|
|
|
|
+ while (ktopic) {
|
|
|
|
+ kfk_topic_t *next = ktopic->next;
|
|
|
|
+
|
|
|
|
+ if (topic_name->len == ktopic->topic_name->len &&
|
|
|
|
+ strncmp(topic_name->s, ktopic->topic_name->s, topic_name->len) == 0) {
|
|
|
|
+ LM_DBG("Topic name match: %.*s\n",
|
|
|
|
+ ktopic->topic_name->len,
|
|
|
|
+ ktopic->topic_name->s);
|
|
|
|
+ result = ktopic->rd_topic;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ktopic = next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+clean:
|
|
|
|
+
|
|
|
|
+ return result;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief send a message to a topic.
|
|
|
|
+ *
|
|
|
|
+ * \param topic_name name of the topic
|
|
|
|
+ * \param message message to send.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+int kfk_message_send(str *topic_name, str *message)
|
|
|
|
+{
|
|
|
|
+ /* Get topic from name. */
|
|
|
|
+ rd_kafka_topic_t *rkt = kfk_topic_get(topic_name);
|
|
|
|
+
|
|
|
|
+ if (!rkt) {
|
|
|
|
+ LM_ERR("Topic not found: %.*s\n", topic_name->len, topic_name->s);
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Send a message. */
|
|
|
|
+ if (rd_kafka_produce(
|
|
|
|
+ rkt,
|
|
|
|
+ RD_KAFKA_PARTITION_UA,
|
|
|
|
+ RD_KAFKA_MSG_F_COPY,
|
|
|
|
+ /* Payload and length */
|
|
|
|
+ message->s,
|
|
|
|
+ message->len,
|
|
|
|
+ /* Optional key and its length */
|
|
|
|
+ NULL, 0,
|
|
|
|
+ /* Message opaque, provided in
|
|
|
|
+ * delivery report callback as
|
|
|
|
+ * msg_opaque. */
|
|
|
|
+ NULL) == -1) {
|
|
|
|
+ rd_kafka_resp_err_t err = rd_kafka_last_error();
|
|
|
|
+ LM_ERR("Error sending message: %s\n", rd_kafka_err2str(err));
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("Message sent\n");
|
|
|
|
+
|
|
|
|
+ /* Poll to handle delivery reports */
|
|
|
|
+ rd_kafka_poll(rk, 0);
|
|
|
|
+ LM_DBG("Message polled\n");
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Initialize statistics.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+int kfk_stats_init()
|
|
|
|
+{
|
|
|
|
+ LM_DBG("Initializing statistics\n");
|
|
|
|
+
|
|
|
|
+ stats_lock = lock_alloc();
|
|
|
|
+ if (!stats_lock) {
|
|
|
|
+ LM_ERR("Cannot allocate stats lock\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(lock_init(stats_lock) == NULL) {
|
|
|
|
+ LM_ERR("cannot init stats lock\n");
|
|
|
|
+ lock_dealloc(stats_lock);
|
|
|
|
+ stats_lock = NULL;
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ stats_general = shm_malloc(sizeof(kfk_stats_t));
|
|
|
|
+ if (!stats_general) {
|
|
|
|
+ LM_ERR("Out of shared memory\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ memset(stats_general, 0, sizeof(kfk_stats_t));
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Close statistics.
|
|
|
|
+ */
|
|
|
|
+void kfk_stats_close()
|
|
|
|
+{
|
|
|
|
+ LM_DBG("Closing statistics\n");
|
|
|
|
+
|
|
|
|
+ if (stats_lock) {
|
|
|
|
+ LM_DBG("Freeing lock\n");
|
|
|
|
+ lock_destroy(stats_lock);
|
|
|
|
+ lock_dealloc(stats_lock);
|
|
|
|
+ stats_lock = NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ kfk_stats_t *current_topic = stats_general;
|
|
|
|
+ while (current_topic) {
|
|
|
|
+ kfk_stats_t *next = current_topic->next;
|
|
|
|
+ kfk_stats_topic_free(current_topic);
|
|
|
|
+ current_topic = next;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief free a kfk_stats_t structure.
|
|
|
|
+ */
|
|
|
|
+static void kfk_stats_topic_free(kfk_stats_t *st_topic)
|
|
|
|
+{
|
|
|
|
+ if (!st_topic) {
|
|
|
|
+ /* Nothing to free. */
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Free topic_name str. */
|
|
|
|
+ if (st_topic->topic_name) {
|
|
|
|
+ if (st_topic->topic_name->s) {
|
|
|
|
+ shm_free(st_topic->topic_name->s);
|
|
|
|
+ }
|
|
|
|
+ shm_free(st_topic->topic_name);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ shm_free(st_topic);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief create a new stats_topic node.
|
|
|
|
+ *
|
|
|
|
+ * \return the new kfk_stats_t on success.
|
|
|
|
+ * \return NULL on error.
|
|
|
|
+ */
|
|
|
|
+static kfk_stats_t* kfk_stats_topic_new(const char *topic, rd_kafka_resp_err_t err)
|
|
|
|
+{
|
|
|
|
+ kfk_stats_t *st = NULL;
|
|
|
|
+
|
|
|
|
+ if (!topic) {
|
|
|
|
+ LM_ERR("No topic\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ int topic_len = strlen(topic);
|
|
|
|
+ if (topic_len == 0) {
|
|
|
|
+ LM_ERR("Void topic\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ st = shm_malloc(sizeof(kfk_stats_t));
|
|
|
|
+ if (!st) {
|
|
|
|
+ LM_ERR("Out of shared memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(st, 0, sizeof(kfk_stats_t));
|
|
|
|
+
|
|
|
|
+ st->topic_name = shm_malloc(sizeof(str));
|
|
|
|
+ if (!st->topic_name) {
|
|
|
|
+ LM_ERR("Out of shared memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memset(st->topic_name, 0, sizeof(str));
|
|
|
|
+
|
|
|
|
+ st->topic_name->s = shm_malloc(topic_len + 1);
|
|
|
|
+ if (!st->topic_name->s) {
|
|
|
|
+ LM_ERR("Out of shared memory\n");
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+ memcpy(st->topic_name->s, topic, topic_len);
|
|
|
|
+ st->topic_name->s[topic_len] = '\0';
|
|
|
|
+ st->topic_name->len = topic_len;
|
|
|
|
+
|
|
|
|
+ st->total++;
|
|
|
|
+ if (err) {
|
|
|
|
+ st->error++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return st;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+
|
|
|
|
+ if (st) {
|
|
|
|
+ kfk_stats_topic_free(st);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief add a new message delivery to statistics.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+static int kfk_stats_add(const char *topic, rd_kafka_resp_err_t err)
|
|
|
|
+{
|
|
|
|
+ LM_DBG("Adding stats: (topic: %s) (error: %d)\n",
|
|
|
|
+ topic, err);
|
|
|
|
+
|
|
|
|
+ if (topic == NULL || *topic == '\0') {
|
|
|
|
+ LM_ERR("No topic to add to statistics\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ int topic_len = strlen(topic);
|
|
|
|
+
|
|
|
|
+ lock_get(stats_lock);
|
|
|
|
+
|
|
|
|
+ stats_general->total++;
|
|
|
|
+
|
|
|
|
+ if (err) {
|
|
|
|
+ stats_general->error++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("General stats: total = %" PRIu64 " error = %" PRIu64 "\n",
|
|
|
|
+ stats_general->total, stats_general->error);
|
|
|
|
+
|
|
|
|
+ kfk_stats_t **stats_pre = &(stats_general->next);
|
|
|
|
+ while (*stats_pre != NULL) {
|
|
|
|
+ LM_DBG("Topic search: %.*s\n", (*stats_pre)->topic_name->len,
|
|
|
|
+ (*stats_pre)->topic_name->s);
|
|
|
|
+ if ((*stats_pre)->topic_name->len == topic_len &&
|
|
|
|
+ strncmp(topic, (*stats_pre)->topic_name->s, (*stats_pre)->topic_name->len) == 0) {
|
|
|
|
+ /* Topic match. */
|
|
|
|
+ LM_DBG("Topic match: %.*s\n", (*stats_pre)->topic_name->len,
|
|
|
|
+ (*stats_pre)->topic_name->s);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ stats_pre = &((*stats_pre)->next);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (*stats_pre == NULL) {
|
|
|
|
+ /* Topic not found. */
|
|
|
|
+ LM_DBG("Topic: %s not found\n", topic);
|
|
|
|
+
|
|
|
|
+ /* Add a new stats topic. */
|
|
|
|
+ kfk_stats_t *new_topic = NULL;
|
|
|
|
+ new_topic = kfk_stats_topic_new(topic, err);
|
|
|
|
+ if (!new_topic) {
|
|
|
|
+ LM_ERR("Failed to create stats for topic: %s\n", topic);
|
|
|
|
+ goto error;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *stats_pre = new_topic;
|
|
|
|
+ LM_DBG("Created Topic stats (%s): total = %" PRIu64 " error = %" PRIu64 "\n",
|
|
|
|
+ topic, new_topic->total, new_topic->error);
|
|
|
|
+
|
|
|
|
+ goto clean;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Topic found. Increase statistics. */
|
|
|
|
+ kfk_stats_t *current = *stats_pre;
|
|
|
|
+ current->total++;
|
|
|
|
+ if (err) {
|
|
|
|
+ current->error++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ LM_DBG("Topic stats (%s): total = %" PRIu64 " error = %" PRIu64 "\n",
|
|
|
|
+ topic, current->total, current->error);
|
|
|
|
+
|
|
|
|
+clean:
|
|
|
|
+ lock_release(stats_lock);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+error:
|
|
|
|
+ lock_release(stats_lock);
|
|
|
|
+
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Get total statistics.
|
|
|
|
+ *
|
|
|
|
+ * \param msg_total return total number of messages by reference.
|
|
|
|
+ * \param msg_error return total number of errors by reference.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+int kfk_stats_get(uint64_t *msg_total, uint64_t *msg_error)
|
|
|
|
+{
|
|
|
|
+ lock_get(stats_lock);
|
|
|
|
+
|
|
|
|
+ *msg_total = stats_general->total;
|
|
|
|
+ *msg_error = stats_general->error;
|
|
|
|
+
|
|
|
|
+ lock_release(stats_lock);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * \brief Get statistics for a specified topic.
|
|
|
|
+ *
|
|
|
|
+ * \param s_topic string with topic name.
|
|
|
|
+ * \param msg_total return total number of messages by reference.
|
|
|
|
+ * \param msg_error return total number of errors by reference.
|
|
|
|
+ *
|
|
|
|
+ * \return 0 on success.
|
|
|
|
+ */
|
|
|
|
+int kfk_stats_topic_get(str *s_topic, uint64_t *msg_total, uint64_t *msg_error)
|
|
|
|
+{
|
|
|
|
+ /* Default return values. */
|
|
|
|
+ *msg_total = 0;
|
|
|
|
+ *msg_error = 0;
|
|
|
|
+
|
|
|
|
+ lock_get(stats_lock);
|
|
|
|
+
|
|
|
|
+ kfk_stats_t *st = stats_general->next;
|
|
|
|
+ while (st) {
|
|
|
|
+ LM_DBG("Topic show search: %.*s\n", st->topic_name->len, st->topic_name->s);
|
|
|
|
+
|
|
|
|
+ if (st->topic_name->len == s_topic->len &&
|
|
|
|
+ strncmp(s_topic->s, st->topic_name->s, s_topic->len) == 0) {
|
|
|
|
+ /* Topic match. */
|
|
|
|
+ LM_DBG("Topic show match: %.*s\n", st->topic_name->len, st->topic_name->s);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ st = st->next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!st) {
|
|
|
|
+ LM_ERR("Topic not found. Showing default 0 values\n");
|
|
|
|
+ goto clean;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *msg_total = st->total;
|
|
|
|
+ *msg_error = st->error;
|
|
|
|
+
|
|
|
|
+clean:
|
|
|
|
+
|
|
|
|
+ lock_release(stats_lock);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|