diff --git a/plugins/README.md b/plugins/README.md index 27c7430e..94f75027 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -16,6 +16,13 @@ This is an **example** plugin that demonstrates a basic authentication callback that allows clients based on their IP address. Password based authentication is preferred over this very simple type of access control. +## Examples / Client lifetime stats +This is an **example** plugin that collects counts of how long client sessions +last, in different time buckets of 0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1k, +2k, 5k, 10k, 20k, 50k, 100k, 200k, 500k, 1M, 2M, 5M, 10M, 20M, 50M, 100M, 200M, +500M seconds. It periodically publishes the counts at +`$SYS/broker/client/lifetimes/`. + ## Examples / Client properties This is an **example** plugin that demonstrates some of the functions for retrieving client information such as client id and username. @@ -56,6 +63,12 @@ This plugin adds the text string "hello " to the beginning of each payload, so with anything other than simple plain text messages it will corrupt the payload contents. +## Examples / Payload size stats +This is an **example** plugin that collects counts of payload message sizes, in +time buckets of 0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1k, 2k, 5k, 10k, 20k, +50k, 100k, 200k, 500k, 1M, 2M, 5M, 10M, 20M, 50M, 100M, 200M, 500M bytes. It +periodically publishes the counts at `$SYS/broker/publish/sizes/`. + ## Examples / Print IP on publish This is an **example** plugin that prints out client ID and IP address of any client that publishes on a particular topic. diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt index 4c01a2a0..6913427e 100644 --- a/plugins/examples/CMakeLists.txt +++ b/plugins/examples/CMakeLists.txt @@ -1,5 +1,6 @@ if(NOT WIN32) add_subdirectory(add-properties) + add_subdirectory(client-lifetime-stats) add_subdirectory(message-timestamp) endif() add_subdirectory(auth-by-ip) @@ -8,5 +9,6 @@ add_subdirectory(connection-state) add_subdirectory(delayed-auth) add_subdirectory(force-retain) add_subdirectory(payload-modification) +add_subdirectory(payload-size-stats) add_subdirectory(print-ip-on-publish) add_subdirectory(topic-modification) diff --git a/plugins/examples/Makefile b/plugins/examples/Makefile index 59bb6c40..84da6435 100644 --- a/plugins/examples/Makefile +++ b/plugins/examples/Makefile @@ -1,12 +1,14 @@ DIRS= \ add-properties \ auth-by-ip \ + client-lifetime-stats \ client-properties \ connection-state \ delayed-auth \ force-retain \ message-timestamp \ payload-modification \ + payload-size-stats \ print-ip-on-publish \ topic-modification \ wildcard-temp diff --git a/plugins/examples/client-lifetime-stats/CMakeLists.txt b/plugins/examples/client-lifetime-stats/CMakeLists.txt new file mode 100644 index 00000000..80459a0b --- /dev/null +++ b/plugins/examples/client-lifetime-stats/CMakeLists.txt @@ -0,0 +1,27 @@ +set (PLUGIN_NAME mosquitto_client_lifetime_stats) + +add_library(${PLUGIN_NAME} MODULE + ${PLUGIN_NAME}.c +) + +target_include_directories(${PLUGIN_NAME} PRIVATE + "${OPENSSL_INCLUDE_DIR}" + "${STDBOOL_H_PATH}" + "${STDINT_H_PATH}" + "${mosquitto_SOURCE_DIR}" + "${mosquitto_SOURCE_DIR}/deps" + "${mosquitto_SOURCE_DIR}/include" +) + +link_directories(${mosquitto_SOURCE_DIR}) + +set_target_properties(${PLUGIN_NAME} PROPERTIES + PREFIX "" + POSITION_INDEPENDENT_CODE 1 +) +if(WIN32) + target_link_libraries(${PLUGIN_NAME} mosquitto) +endif() + +# Don't install, these are example plugins only. +#install(TARGETS ${PLUGIN_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") diff --git a/plugins/examples/client-lifetime-stats/Makefile b/plugins/examples/client-lifetime-stats/Makefile new file mode 100644 index 00000000..95da6df2 --- /dev/null +++ b/plugins/examples/client-lifetime-stats/Makefile @@ -0,0 +1,28 @@ +include ../../../config.mk + +.PHONY : all binary check clean reallyclean test install uninstall + +PLUGIN_NAME=mosquitto_client_lifetime_stats +PLUGIN_CFLAGS+=-I../../../include -I../../../ -I../../../deps + +all : binary + +binary : ${PLUGIN_NAME}.so + +${PLUGIN_NAME}.so : ${PLUGIN_NAME}.c + $(CROSS_COMPILE)$(CC) $(PLUGIN_CPPFLAGS) $(PLUGIN_CFLAGS) $(PLUGIN_LDFLAGS) -fPIC -shared $< -o $@ + +reallyclean : clean +clean: + -rm -f *.o ${PLUGIN_NAME}.so *.gcda *.gcno + +check: test +test: + +install: ${PLUGIN_NAME}.so + # Don't install, these are examples only. + $(INSTALL) -d "${DESTDIR}$(libdir)" + $(INSTALL) ${STRIP_OPTS} ${PLUGIN_NAME}.so "${DESTDIR}${libdir}/${PLUGIN_NAME}.so" + +uninstall : + -rm -f "${DESTDIR}${libdir}/${PLUGIN_NAME}.so" diff --git a/plugins/examples/client-lifetime-stats/mosquitto_client_lifetime_stats.c b/plugins/examples/client-lifetime-stats/mosquitto_client_lifetime_stats.c new file mode 100644 index 00000000..aeb6b2be --- /dev/null +++ b/plugins/examples/client-lifetime-stats/mosquitto_client_lifetime_stats.c @@ -0,0 +1,197 @@ +/* +Copyright (c) 2021 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License 2.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + https://www.eclipse.org/legal/epl-2.0/ +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +/* + * Publish statistics on client session lifetimes. + * + * Compile with: + * gcc -I -fPIC -shared mosquitto_client_lifetime_stats.c -o mosquitto_client_lifetime_stats.so + * + * Use in config with: + * + * plugin /path/to/mosquitto_client_lifetime_stats.so + * + * Note that this only works on Mosquitto 2.0 or later. + */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mosquitto.h" +#include "mqtt_protocol.h" + +MOSQUITTO_PLUGIN_DECLARE_VERSION(5); + +static mosquitto_plugin_id_t *mosq_pid = NULL; + +struct lifetime_s{ + UT_hash_handle hh; + char *id; + time_t connect; +}; +struct lifetime_s *local_lifetimes = NULL; + +#define LIFETIME_COUNT 28 +static const char *lifetime_strs[LIFETIME_COUNT] = { + "0", + "1", "2", "5", + "10", "20", "50", + "100", "200", "500", + "1k", "2k", "5k", + "10k", "20k", "50k", + "100k", "200k", "500k", + "1M", "2M", "5M", + "10M", "20M", "50M", + "100M", "200M", "500M" +}; + +static uint32_t lifetime_values[LIFETIME_COUNT] = { + 0, + 1, 2, 5, + 10, 20, 50, + 100, 200, 500, + 1000, 2000, 5000, + 10000, 20000, 50000, + 100000, 200000, 500000, + 1000000, 2000000, 5000000, + 10000000, 20000000, 50000000, + 100000000, 200000000, 500000000 +}; + +static long lifetime_counts[LIFETIME_COUNT]; +static long last_lifetime_counts[LIFETIME_COUNT]; +static time_t last_report = 0; + + +static int callback_tick(int event, void *event_data, void *userdata) +{ + struct timespec ts; + char topic[40]; + char payload[40]; + int slen; + int i; + + UNUSED(event); + UNUSED(event_data); + UNUSED(userdata); + + clock_gettime(CLOCK_REALTIME, &ts); + if(last_report + 10 < ts.tv_sec){ + last_report = ts.tv_sec; + + for(i=0; iclient); + if(id){ + HASH_FIND(hh, local_lifetimes, id, strlen(id), client); + if(!client){ + client = malloc(sizeof(struct lifetime_s)); + if(client == NULL){ + return MOSQ_ERR_SUCCESS; + } + client->id = strdup(id); + if(client->id == NULL){ + free(client); + return MOSQ_ERR_SUCCESS; + } + client->connect = time(NULL); + HASH_ADD_KEYPTR(hh, local_lifetimes, client->id, strlen(client->id), client); + } + } + + return MOSQ_ERR_SUCCESS; +} + + +static int callback_disconnect(int event, void *event_data, void *userdata) +{ + struct mosquitto_evt_disconnect *ed = event_data; + int i; + const char *id; + struct lifetime_s *client; + time_t lifetime; + + UNUSED(event); + UNUSED(userdata); + + id = mosquitto_client_id(ed->client); + if(id){ + HASH_FIND(hh, local_lifetimes, id, strlen(id), client); + if(client){ + HASH_DELETE(hh, local_lifetimes, client); + + lifetime = time(NULL) - client->connect; + free(client->id); + free(client); + + for(i=0; i + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License 2.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + https://www.eclipse.org/legal/epl-2.0/ +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +/* + * Publish statistics on message payload size. + * + * Compile with: + * gcc -I -fPIC -shared mosquitto_payload_size_stats.c -o mosquitto_payload_size_stats.so + * + * Use in config with: + * + * plugin /path/to/mosquitto_payload_size_stats.so + * + * Note that this only works on Mosquitto 2.0 or later. + */ +#include "config.h" + +#include +#include +#include + +#include "mosquitto_broker.h" +#include "mosquitto_plugin.h" +#include "mosquitto.h" +#include "mqtt_protocol.h" + +MOSQUITTO_PLUGIN_DECLARE_VERSION(5); + +static mosquitto_plugin_id_t *mosq_pid = NULL; + +#define SIZE_COUNT 28 +static const char *size_strs[SIZE_COUNT] = { + "0", + "1", "2", "5", + "10", "20", "50", + "100", "200", "500", + "1k", "2k", "5k", + "10k", "20k", "50k", + "100k", "200k", "500k", + "1M", "2M", "5M", + "10M", "20M", "50M", + "100M", "200M", "500M" +}; + +static uint32_t size_values[SIZE_COUNT] = { + 0, + 1, 2, 5, + 10, 20, 50, + 100, 200, 500, + 1000, 2000, 5000, + 10000, 20000, 50000, + 100000, 200000, 500000, + 1000000, 2000000, 5000000, + 10000000, 20000000, 50000000, + 100000000, 200000000, 500000000 +}; + +static long size_counts[SIZE_COUNT]; +static long last_size_counts[SIZE_COUNT]; +static time_t last_report = 0; + + +static int callback_tick(int event, void *event_data, void *userdata) +{ + struct timespec ts; + char topic[40]; + char payload[40]; + int slen; + int i; + + UNUSED(event); + UNUSED(event_data); + UNUSED(userdata); + + clock_gettime(CLOCK_REALTIME, &ts); + if(last_report + 10 < ts.tv_sec){ + last_report = ts.tv_sec; + + for(i=0; ipayloadlen <= size_values[i]){ + size_counts[i]++; + break; + } + } + + return MOSQ_ERR_SUCCESS; +} + + +int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count) +{ + UNUSED(user_data); + UNUSED(opts); + UNUSED(opt_count); + + memset(size_counts, 0, sizeof(size_counts)); + memset(last_size_counts, 0, sizeof(last_size_counts)); + + mosq_pid = identifier; + mosquitto_callback_register(mosq_pid, MOSQ_EVT_MESSAGE, callback_message, NULL, NULL); + mosquitto_callback_register(mosq_pid, MOSQ_EVT_TICK, callback_tick, NULL, NULL); + + return MOSQ_ERR_SUCCESS; +} diff --git a/plugins/examples/payload-size-stats/test.conf b/plugins/examples/payload-size-stats/test.conf new file mode 100644 index 00000000..5a6d80b2 --- /dev/null +++ b/plugins/examples/payload-size-stats/test.conf @@ -0,0 +1 @@ +plugin ./mosquitto_payload_size_stats.so diff --git a/plugins/examples/payload-size-stats/test.sh b/plugins/examples/payload-size-stats/test.sh new file mode 100755 index 00000000..8589ba43 --- /dev/null +++ b/plugins/examples/payload-size-stats/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +../../../src/mosquitto -c test.conf -v