From 064fa48e2c251cff92bae8c2ff8791deb5656d52 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 2 Sep 2026 16:53:54 -0700 Subject: [PATCH 1/5] refactor: name the poller_item scoping rule that poll_host duplicates poll_host() is 1,926 lines and builds the same six queries twice, once for the main poller and once for a remote one. The bodies differ by 33 of 141 lines, and every difference is the same rule: the main poller reads items that are not deleted, a remote poller reads the items assigned to it. Nothing in that construction was reachable from a test, so a column added to one copy and not the other would not have been caught. Extract the rule as poller_item_scope() and poller_owner_scope(), covered by five cases in test_linked against the shipped poller.o: the deleted filter on the main poller, the ownership filter on a remote one, the empty fragment the main poller needs so callers can interpolate unconditionally, and a degenerate buffer. No call site changes yet. Collapsing the two branches onto these helpers is the next step and is worth its own review, because the shipped query1 spells its tail 'poller_id=%i' while the others spell it 'poller_id = %i', so the unified text will normalise that. Signed-off-by: Thomas Vincent --- poller.c | 39 +++++++++++++++++++++++ spine.h | 3 ++ tests/unit/test_linked.c | 67 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/poller.c b/poller.c index b03cd6e2..ff205631 100644 --- a/poller.c +++ b/poller.c @@ -117,6 +117,45 @@ void *child(void *arg) { exit(0); } +/*! \fn void poller_item_scope(char *out, size_t len, int poller_id) + * \brief The poller_item filter for the poller this process is running as. + * + * The main poller reads every item that has not been deleted. A remote poller + * reads the items assigned to it, and ownership already excludes deleted rows. + * Kept in one place because poll_host() built the same pair of queries twice, + * once per branch, and a column added to one copy would not reach the other. + */ +void poller_item_scope(char *out, size_t len, int poller_id) { + if (out == NULL || len == 0) { + return; + } + + if (poller_id == 0) { + snprintf(out, len, " AND deleted = ''"); + } else { + snprintf(out, len, " AND poller_id = %i", poller_id); + } +} + +/*! \fn void poller_owner_scope(char *out, size_t len, int poller_id) + * \brief The ownership filter applied only by a remote poller. + * + * The main poller does not constrain these queries at all; a remote poller + * restricts them to its own rows. Returns an empty string for the main poller + * so the caller can interpolate it unconditionally. + */ +void poller_owner_scope(char *out, size_t len, int poller_id) { + if (out == NULL || len == 0) { + return; + } + + if (poller_id == 0) { + out[0] = '\0'; + } else { + snprintf(out, len, " AND poller_id = %i", poller_id); + } +} + /*! \fn void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) * \brief core Spine function that polls a host * \param host_id integer value for the host_id from the hosts table in Cacti diff --git a/spine.h b/spine.h index 7344424c..2a05bcf3 100644 --- a/spine.h +++ b/spine.h @@ -626,6 +626,9 @@ typedef struct db_connection { #include "error.h" /* Globals */ +extern void poller_item_scope(char *out, size_t len, int poller_id); +extern void poller_owner_scope(char *out, size_t len, int poller_id); + extern config_t set; extern php_t *php_processes; extern char start_datetime[20]; diff --git a/tests/unit/test_linked.c b/tests/unit/test_linked.c index 71371452..0272e0a2 100644 --- a/tests/unit/test_linked.c +++ b/tests/unit/test_linked.c @@ -457,6 +457,68 @@ static void test_is_debug_device_matches_only_listed_ids(void **state) { debug_devices = saved; } + +/* poll_host() built the same six queries twice, once for the main poller and + * once for a remote one, differing only in how each query is scoped. A column + * added to one copy would not have reached the other, and none of it was + * reachable from a test. These two helpers hold the scoping rule. + */ +static void test_poller_item_scope_filters_deleted_on_the_main_poller(void **state) { + char scope[64]; + (void) state; + + poller_item_scope(scope, sizeof scope, 0); + assert_string_equal(scope, " AND deleted = ''"); +} + +static void test_poller_item_scope_filters_by_owner_on_a_remote_poller(void **state) { + char scope[64]; + (void) state; + + poller_item_scope(scope, sizeof scope, 3); + assert_string_equal(scope, " AND poller_id = 3"); + + poller_item_scope(scope, sizeof scope, 1); + assert_string_equal(scope, " AND poller_id = 1"); +} + +/* The main poller does not constrain the ownership queries at all, so the + * fragment has to be empty rather than absent: the caller interpolates it + * unconditionally. */ +static void test_poller_owner_scope_is_empty_on_the_main_poller(void **state) { + char scope[64]; + (void) state; + + memcpy(scope, "stale", 6); + poller_owner_scope(scope, sizeof scope, 0); + assert_string_equal(scope, ""); +} + +static void test_poller_owner_scope_names_the_remote_poller(void **state) { + char scope[64]; + (void) state; + + poller_owner_scope(scope, sizeof scope, 7); + assert_string_equal(scope, " AND poller_id = 7"); +} + +/* Both helpers are handed fixed stack buffers by poll_host(), so a degenerate + * size must not write. */ +static void test_poller_scopes_refuse_a_degenerate_buffer(void **state) { + char scope[8]; + (void) state; + + memcpy(scope, "keep", 5); + poller_item_scope(scope, 0, 0); + assert_string_equal(scope, "keep"); + + poller_owner_scope(scope, 0, 4); + assert_string_equal(scope, "keep"); + + poller_item_scope(NULL, sizeof scope, 0); + poller_owner_scope(NULL, sizeof scope, 0); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strncopy_truncates_within_the_buffer), @@ -496,6 +558,11 @@ int main(void) { cmocka_unit_test(test_get_date_format_clamps_an_out_of_range_format), cmocka_unit_test(test_get_date_format_covers_each_supported_format), cmocka_unit_test(test_is_debug_device_matches_only_listed_ids), + cmocka_unit_test(test_poller_item_scope_filters_deleted_on_the_main_poller), + cmocka_unit_test(test_poller_item_scope_filters_by_owner_on_a_remote_poller), + cmocka_unit_test(test_poller_owner_scope_is_empty_on_the_main_poller), + cmocka_unit_test(test_poller_owner_scope_names_the_remote_poller), + cmocka_unit_test(test_poller_scopes_refuse_a_degenerate_buffer), }; return cmocka_run_group_tests(tests, NULL, NULL); From db7f9972ac4d9d73aa39cf82a48df46e4ddca918 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 2 Sep 2026 17:04:56 -0700 Subject: [PATCH 2/5] refactor: build poll_host queries once instead of per poller type The two branches drifted: the remote copy never picked up the dbonupdate handling the main copy grew, and nothing could have caught that because the construction was not reachable from a test. Captured what both branches emit across the vectors they switch on, collapsed them onto poller_item_scope()/poller_owner_scope(), and diffed. Output is byte-identical except query1 on a remote poller, which now spells its tail 'poller_id = N' rather than 'poller_id=N' to match the other five queries. Signed-off-by: Thomas Vincent --- poller.c | 342 ++++++++------------------ tests/golden/README.md | 20 ++ tests/golden/poll_host_queries.golden | 152 ++++++++++++ 3 files changed, 277 insertions(+), 237 deletions(-) create mode 100644 tests/golden/README.md create mode 100644 tests/golden/poll_host_queries.golden diff --git a/poller.c b/poller.c index ff205631..e9b615a9 100644 --- a/poller.c +++ b/poller.c @@ -222,6 +222,8 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread char temp_poll_result[BUFSIZE]; char temp_arg1[BUFSIZE]; char limits[SMALL_BUFSIZE]; + char item_scope[SMALL_BUFSIZE]; + char owner_scope[SMALL_BUFSIZE]; int last_snmp_version = 0; int last_snmp_port = 0; @@ -315,10 +317,63 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* optional output_regex column (added in Cacti 1.3.1) */ const char *regex_col = set.has_output_regex ? ", output_regex" : ""; - /* single polling interval query for items */ - if (set.poller_id == 0) { + /* Scope every poller_item read to what this poller owns: the main poller + takes every undeleted row, a remote poller takes the rows assigned to it. + Both fragments are interpolated unconditionally, so the queries below do + not branch on poller_id and cannot drift apart. */ + poller_item_scope(item_scope, sizeof(item_scope), set.poller_id); + poller_owner_scope(owner_scope, sizeof(owner_scope), set.poller_id); + + if (set.total_snmp_ports == 1) { + snprintf(query1, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s %s", regex_col, host_id, item_scope, limits); + } else { + snprintf(query1, BUFSIZE, + "SELECT SQL_NO_CACHE action, hostname, snmp_community, " + "snmp_version, snmp_username, snmp_password, " + "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " + "rrd_num, snmp_port, snmp_timeout, " + "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" + "%s" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, item_scope, limits); + } + + /* host structure for uptime checks */ + snprintf(query2, BIG_BUFSIZE, + "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " + "snmp_username, snmp_password, snmp_auth_protocol, " + "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " + "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " + "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " + "UNIX_TIMESTAMP(status_rec_date), status_last_error, " + "min_time, max_time, cur_time, avg_time, " + "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " + "snmp_sysContact, snmp_sysName, snmp_sysLocation" + " FROM host" + " WHERE id = %i" + " AND deleted = ''", host_id); + + /* data query structure for reindex detection */ + snprintf(query4, BUFSIZE, + "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" + " FROM poller_reindex" + " WHERE host_id = %i", host_id); + + /* multiple polling interval query for items */ + if (set.active_profiles != 1) { if (set.total_snmp_ports == 1) { - snprintf(query1, BUFSIZE, + snprintf(query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -327,9 +382,10 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread "%s" " FROM poller_item" " WHERE host_id = %i" - " AND deleted = '' %s", regex_col, host_id, limits); + " AND rrd_next_step <= 0" + "%s %s", regex_col, host_id, owner_scope, limits); } else { - snprintf(query1, BUFSIZE, + snprintf(query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -338,129 +394,13 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread "%s" " FROM poller_item" " WHERE host_id = %i" - " AND deleted = ''" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - - /* host structure for uptime checks */ - snprintf(query2, BIG_BUFSIZE, - "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " - "snmp_username, snmp_password, snmp_auth_protocol, " - "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " - "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " - "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " - "UNIX_TIMESTAMP(status_rec_date), status_last_error, " - "min_time, max_time, cur_time, avg_time, " - "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " - "snmp_sysContact, snmp_sysName, snmp_sysLocation" - " FROM host" - " WHERE id = %i" - " AND deleted = ''", host_id); - - /* data query structure for reindex detection */ - snprintf(query4, BUFSIZE, - "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" - " FROM poller_reindex" - " WHERE host_id = %i", host_id); - - /* multiple polling interval query for items */ - if (set.active_profiles != 1) { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " %s", regex_col, host_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - } else { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " %s", regex_col, host_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, limits); - } - } - - /* query to setup the next polling interval in cacti */ - snprintf(query6, BUFSIZE, - "UPDATE poller_item" - " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" - " WHERE host_id = %i", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id); - - /* query to add output records to the poller output table */ - snprintf(query8, BUFSIZE, - "INSERT INTO poller_output" - " (local_data_id, rrd_name, time, output) VALUES"); - - /* query suffix to add rows to the poller output table */ - if (set.dbonupdate == 0) { - snprintf(posuffix, BUFSIZE, - " ON DUPLICATE KEY UPDATE output=VALUES(output)"); - } else { - snprintf(posuffix, BUFSIZE, - " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); - } - - /* number of agent's count for single polling interval */ - snprintf(query9, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " GROUP BY snmp_port %s", host_id, limits); - - /* number of agent's count for multiple polling intervals */ - if (set.active_profiles != 1) { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" " AND rrd_next_step <= 0" - " GROUP BY snmp_port %s", host_id, limits); - } else { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " GROUP BY snmp_port %s", host_id, limits); + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, owner_scope, limits); } } else { if (set.total_snmp_ports == 1) { - snprintf(query1, BUFSIZE, + snprintf(query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -469,9 +409,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread "%s" " FROM poller_item" " WHERE host_id = %i" - " AND poller_id=%i %s", regex_col, host_id, set.poller_id, limits); + "%s %s", regex_col, host_id, owner_scope, limits); } else { - snprintf(query1, BUFSIZE, + snprintf(query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -480,127 +420,55 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread "%s" " FROM poller_item" " WHERE host_id = %i" - " AND poller_id=%i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } - - /* host structure for uptime checks */ - snprintf(query2, BIG_BUFSIZE, - "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " - "snmp_username, snmp_password, snmp_auth_protocol, " - "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " - "availability_method, ping_method, ping_port, ping_timeout, ping_retries, " - "status, status_event_count, UNIX_TIMESTAMP(status_fail_date), " - "UNIX_TIMESTAMP(status_rec_date), status_last_error, " - "min_time, max_time, cur_time, avg_time, " - "total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, " - "snmp_sysContact, snmp_sysName, snmp_sysLocation" - " FROM host" - " WHERE id = %i" - " AND deleted = ''", host_id); - - /* data query structure for reindex detection */ - snprintf(query4, BUFSIZE, - "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" - " FROM poller_reindex" - " WHERE host_id = %i", host_id); - - /* multiple polling interval query for items */ - if (set.active_profiles != 1) { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i %s", regex_col, host_id, set.poller_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } - } else { - if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i %s", regex_col, host_id, set.poller_id, limits); - } else { - snprintf(query5, BUFSIZE, - "SELECT SQL_NO_CACHE action, hostname, snmp_community, " - "snmp_version, snmp_username, snmp_password, " - "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " - "rrd_num, snmp_port, snmp_timeout, " - "snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id" - "%s" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i" - " ORDER BY snmp_port %s", regex_col, host_id, set.poller_id, limits); - } + "%s" + " ORDER BY snmp_port %s", regex_col, host_id, owner_scope, limits); } + } - /* query to setup the next polling interval in cacti */ - snprintf(query6, BUFSIZE, - "UPDATE poller_item" - " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" - " WHERE host_id = %i" - " AND poller_id = %i", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id, set.poller_id); + /* query to setup the next polling interval in cacti */ + snprintf(query6, BUFSIZE, + "UPDATE poller_item" + " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" + " WHERE host_id = %i%s", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id, owner_scope); - /* query to add output records to the poller output table */ - snprintf(query8, BUFSIZE, - "INSERT INTO poller_output" - " (local_data_id, rrd_name, time, output) VALUES"); + /* query to add output records to the poller output table */ + snprintf(query8, BUFSIZE, + "INSERT INTO poller_output" + " (local_data_id, rrd_name, time, output) VALUES"); - /* query suffix to add rows to the poller output table */ + /* query suffix to add rows to the poller output table */ + if (set.poller_id != 0 || set.dbonupdate == 0) { snprintf(posuffix, BUFSIZE, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + } else { + snprintf(posuffix, BUFSIZE, + " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); + } - /* number of agent's count for single polling interval */ - snprintf(query9, BUFSIZE, + /* number of agent's count for single polling interval */ + snprintf(query9, BUFSIZE, + "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); + + /* number of agent's count for multiple polling intervals */ + if (set.active_profiles != 1) { + snprintf(query10, BUFSIZE, "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" " FROM poller_item" " WHERE host_id = %i" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - - /* number of agent's count for multiple polling intervals */ - if (set.active_profiles != 1) { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND rrd_next_step <= 0" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - } else { - snprintf(query10, BUFSIZE, - "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" - " FROM poller_item" - " WHERE host_id = %i" - " AND poller_id = %i" - " GROUP BY snmp_port %s", host_id, set.poller_id, limits); - } + " AND rrd_next_step <= 0" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); + } else { + snprintf(query10, BUFSIZE, + "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" + " FROM poller_item" + " WHERE host_id = %i" + "%s" + " GROUP BY snmp_port %s", host_id, owner_scope, limits); } /* query to add output records to the poller output table */ diff --git a/tests/golden/README.md b/tests/golden/README.md new file mode 100644 index 00000000..8258a85b --- /dev/null +++ b/tests/golden/README.md @@ -0,0 +1,20 @@ +# Golden capture for poll_host() query construction + +`poll_host()` is the largest function in the tree and builds its SQL inline, so +the construction cannot be reached from `make check`. Before changing it, pin +what it currently emits. + +`poll_host_queries.golden` is the output of every query `poll_host()` builds, +across the input vectors it branches on: `total_snmp_ports` 1 and 2, +`dbonupdate` 0 and 1, and a main poller and a remote poller. + +To recapture, lift the query-building body out of `poller.c` into a standalone +`main()` that declares the handful of inputs it reads (`host_id`, `regex_col`, +`limits`, and `set.poller_id`, `set.total_snmp_ports`, `set.dbonupdate`, +`set.poller_interval`, `set.active_profiles`), print each buffer, and diff the +result against this file. The body compiles standalone: that closed input set is +what makes the extraction safe. + +Use it the same way for the next piece of `poll_host()` that gets extracted. +Capture first, refactor second, and require the diff to be empty or to contain +only changes you can name in advance. diff --git a/tests/golden/poll_host_queries.golden b/tests/golden/poll_host_queries.golden new file mode 100644 index 00000000..f33a4137 --- /dev/null +++ b/tests/golden/poll_host_queries.golden @@ -0,0 +1,152 @@ +== MAIN ports=1 onupd=0 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== MAIN ports=1 onupd=1 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output +== MAIN ports=2 onupd=0 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' ORDER BY snmp_port LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 ORDER BY snmp_port LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== MAIN ports=2 onupd=1 == +### main query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND deleted = '' ORDER BY snmp_port LIMIT 0,100 +### main query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### main query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### main query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 ORDER BY snmp_port LIMIT 0,100 +### main query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 +### main query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### main query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 GROUP BY snmp_port LIMIT 0,100 +### main posuffix + AS rs ON DUPLICATE KEY UPDATE output=rs.output +== REMOTE ports=1 onupd=0 pid=3 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=3 LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 3 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== REMOTE ports=1 onupd=1 pid=7 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=7 LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 7 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== REMOTE ports=2 onupd=0 pid=3 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=3 ORDER BY snmp_port LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 ORDER BY snmp_port LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 3 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 3 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) +== REMOTE ports=2 onupd=1 pid=7 == +### remote query1 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=7 ORDER BY snmp_port LIMIT 0,100 +### remote query2 +SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' +### remote query4 +SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = 42 +### remote query5 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 ORDER BY snmp_port LIMIT 0,100 +### remote query6 +UPDATE poller_item SET rrd_next_step = IF(rrd_step = 60, 0, IF(rrd_next_step - 60 < 0, rrd_step - 60, rrd_next_step - 60)) WHERE host_id = 42 AND poller_id = 7 +### remote query8 +INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES +### remote query9 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote query10 +SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 +### remote posuffix + ON DUPLICATE KEY UPDATE output=VALUES(output) From 8ed27a5ee5384ffc9280cff6d651d6ff318be889 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 2 Sep 2026 17:05:37 -0700 Subject: [PATCH 3/5] fix: apply dbonupdate to the remote poller_output upsert set.dbonupdate is 1 on MySQL 8, which deprecated VALUES() in ON DUPLICATE KEY UPDATE. The main poller switched to the row-alias form; the remote branch kept the deprecated one because it had its own copy of the suffix. Closes #590. Signed-off-by: Thomas Vincent --- poller.c | 2 +- tests/golden/poll_host_queries.golden | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poller.c b/poller.c index e9b615a9..265b609c 100644 --- a/poller.c +++ b/poller.c @@ -437,7 +437,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread " (local_data_id, rrd_name, time, output) VALUES"); /* query suffix to add rows to the poller output table */ - if (set.poller_id != 0 || set.dbonupdate == 0) { + if (set.dbonupdate == 0) { snprintf(posuffix, BUFSIZE, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); } else { diff --git a/tests/golden/poll_host_queries.golden b/tests/golden/poll_host_queries.golden index f33a4137..c75d0b26 100644 --- a/tests/golden/poll_host_queries.golden +++ b/tests/golden/poll_host_queries.golden @@ -76,7 +76,7 @@ SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = AS rs ON DUPLICATE KEY UPDATE output=rs.output == REMOTE ports=1 onupd=0 pid=3 == ### remote query1 -SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=3 LIMIT 0,100 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 LIMIT 0,100 ### remote query2 SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' ### remote query4 @@ -95,7 +95,7 @@ SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = ON DUPLICATE KEY UPDATE output=VALUES(output) == REMOTE ports=1 onupd=1 pid=7 == ### remote query1 -SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=7 LIMIT 0,100 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 LIMIT 0,100 ### remote query2 SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' ### remote query4 @@ -111,10 +111,10 @@ SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = ### remote query10 SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 ### remote posuffix - ON DUPLICATE KEY UPDATE output=VALUES(output) + AS rs ON DUPLICATE KEY UPDATE output=rs.output == REMOTE ports=2 onupd=0 pid=3 == ### remote query1 -SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=3 ORDER BY snmp_port LIMIT 0,100 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 3 ORDER BY snmp_port LIMIT 0,100 ### remote query2 SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' ### remote query4 @@ -133,7 +133,7 @@ SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = ON DUPLICATE KEY UPDATE output=VALUES(output) == REMOTE ports=2 onupd=1 pid=7 == ### remote query1 -SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id=7 ORDER BY snmp_port LIMIT 0,100 +SELECT SQL_NO_CACHE action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, 'RE' AS re FROM poller_item WHERE host_id = 42 AND poller_id = 7 ORDER BY snmp_port LIMIT 0,100 ### remote query2 SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = 42 AND deleted = '' ### remote query4 @@ -149,4 +149,4 @@ SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = ### remote query10 SELECT SQL_NO_CACHE snmp_port, count(snmp_port) FROM poller_item WHERE host_id = 42 AND poller_id = 7 GROUP BY snmp_port LIMIT 0,100 ### remote posuffix - ON DUPLICATE KEY UPDATE output=VALUES(output) + AS rs ON DUPLICATE KEY UPDATE output=rs.output From 869a152924fe3e7b69190e8aed492dd99ddaa3ed Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 2 Sep 2026 17:35:18 -0700 Subject: [PATCH 4/5] refactor: make poll_host's query construction a callable unit The construction was 167 lines in the middle of a 1,795-line function, so nothing could reach it. It now takes its inputs as arguments and fills a struct, which is what lets the golden capture become a test that runs instead of a file with instructions attached. poll_host is 1,620 lines, down from 1,927 on develop. Signed-off-by: Thomas Vincent --- poller.c | 386 ++++++++++++++++++++------------------- spine.h | 22 +++ tests/golden/README.md | 38 ++-- tests/unit/test_linked.c | 208 +++++++++++++++++++++ 4 files changed, 448 insertions(+), 206 deletions(-) diff --git a/poller.c b/poller.c index 265b609c..e9784ad6 100644 --- a/poller.c +++ b/poller.c @@ -156,167 +156,21 @@ void poller_owner_scope(char *out, size_t len, int poller_id) { } } -/*! \fn void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) - * \brief core Spine function that polls a host - * \param host_id integer value for the host_id from the hosts table in Cacti - * - * This function is core to Spine. It will take a host_id and then poll it. - * - * Prior to the poll, the system will ping the host to verify that it is up. - * In addition, the system will check to see if any reindexing of data query's - * is required. - * - * If reindexing is required, the Cacti poller.php function will spawn that - * reindexing process. + +/*! \fn void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits) + * \brief build every query poll_host() issues for one device * - * In the case of hosts that require reindexing because of a sysUptime - * rollback, Spine will store an unknown (NaN) value for all objects to prevent - * spikes in the graphs. - * - * With regard to snmp calls, if the host has multiple snmp agents running - * Spine will re-initialize the snmp session and poll under those new ports - * as the host poller_items table dictates. + * Split out of poll_host() so the construction can be reached from a test. + * It reads only its arguments and the settings named below, which is what + * makes it checkable: given the same inputs it produces the same SQL. * + * Reads set.poller_id, set.total_snmp_ports, set.dbonupdate, + * set.poller_interval and set.active_profiles. */ -void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) { - char query1[BUFSIZE]; - char query2[BIG_BUFSIZE]; - char *query3 = NULL; - char query4[BUFSIZE]; - char query5[BUFSIZE]; - char query6[BUFSIZE]; - char query8[BUFSIZE]; - char query9[BUFSIZE]; - char query10[BUFSIZE]; - char query11[BUFSIZE]; - char *query12 = NULL; - char posuffix[BUFSIZE]; - - int query8_len = 0; - int query11_len = 0; - int posuffix_len = 0; - - char sysUptime[BUFSIZE]; - char result_string[RESULTS_BUFFER+SMALL_BUFSIZE]; - int result_length; - char temp_result[RESULTS_BUFFER]; - int errors = 0; - int *buf_errors; - int *buf_size; - char *error_string; - - int num_rows; - int assert_fail = FALSE; - int reindex_err = FALSE; - int spike_kill = FALSE; - int rows_processed = 0; - int i = 0; - int j = 0; - int k = 0; - int num_oids = 0; - size_t out_buffer; - int php_process; - - char *poll_result = NULL; - char update_sql[BIG_BUFSIZE]; - char temp_poll_result[BUFSIZE]; - char temp_arg1[BUFSIZE]; - char limits[SMALL_BUFSIZE]; +void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits) { char item_scope[SMALL_BUFSIZE]; char owner_scope[SMALL_BUFSIZE]; - int last_snmp_version = 0; - int last_snmp_port = 0; - char last_snmp_community[50]; - char last_snmp_username[50]; - char last_snmp_password[50]; - char last_snmp_auth_protocol[7]; - char last_snmp_priv_passphrase[200]; - char last_snmp_priv_protocol[8]; - char last_snmp_context[65]; - char last_snmp_engine_id[30]; - double poll_time = get_time_as_double(); - double thread_start = 0; - double thread_end = 0; - - /* reindex shortcuts to speed polling */ - int previous_assert_failure = FALSE; - int last_data_query_id = 0; - int perform_assert = TRUE; - int new_buffer = TRUE; - int ignore_sysinfo = TRUE; - int buf_length = 0; - - extern poller_thread_t** details; - - pool_t *local_cnn = NULL; - pool_t *remote_cnn = NULL; - - reindex_t *reindex = NULL; - host_t *host = NULL; - ping_t *ping = NULL; - name_t *name = NULL; - target_t *poller_items = NULL; - snmp_oids_t *snmp_oids = NULL; - - if (!(error_string = malloc(DBL_BUFSIZE))) { - die("ERROR: Fatal malloc error: poller.c error_string!"); - } - if (!(buf_size = malloc(sizeof(int)))) { - die("ERROR: Fatal malloc error: poller.c buf_size!"); - } - if (!(buf_errors = malloc(sizeof(int)))) { - die("ERROR: Fatal malloc error: poller.c buf_errors!"); - } - - *buf_size = 0; - *buf_errors = 0; - - MYSQL mysql; - MYSQL mysqlr; - MYSQL mysqlt; - MYSQL_RES *result; - MYSQL_ROW row; - - //db_connect(LOCAL, &mysql); - local_cnn = db_get_connection(LOCAL); - mysql = local_cnn->mysql; - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - remote_cnn = db_get_connection(REMOTE); - mysqlr = remote_cnn->mysql; - } - - /* allocate host and ping structures with appropriate values */ - if (!(host = (host_t *) malloc(sizeof(host_t)))) { - die("ERROR: Fatal malloc error: poller.c host struct!"); - } - - /* set zeros */ - memset(host, 0, sizeof(host_t)); - - if (!(ping = (ping_t *) malloc(sizeof(ping_t)))) { - die("ERROR: Fatal malloc error: poller.c ping struct!"); - } - - /* set zeros */ - memset(ping, 0, sizeof(ping_t)); - - if (!(reindex = (reindex_t *) malloc(sizeof(reindex_t)))) { - die("ERROR: Fatal malloc error: poller.c reindex poll!"); - } - memset(reindex, 0, sizeof(reindex_t)); - - /* determine the SQL limits using the poller instructions */ - if (host_data_ids > 0) { - snprintf(limits, SMALL_BUFSIZE, "LIMIT %i, %i", host_data_ids * (host_thread - 1), host_data_ids); - } else { - limits[0] = '\0'; - } - - /* optional output_regex column (added in Cacti 1.3.1) */ - const char *regex_col = set.has_output_regex ? ", output_regex" : ""; - /* Scope every poller_item read to what this poller owns: the main poller takes every undeleted row, a remote poller takes the rows assigned to it. Both fragments are interpolated unconditionally, so the queries below do @@ -325,7 +179,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread poller_owner_scope(owner_scope, sizeof(owner_scope), set.poller_id); if (set.total_snmp_ports == 1) { - snprintf(query1, BUFSIZE, + snprintf(q->query1, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -336,7 +190,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread " WHERE host_id = %i" "%s %s", regex_col, host_id, item_scope, limits); } else { - snprintf(query1, BUFSIZE, + snprintf(q->query1, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -350,7 +204,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } /* host structure for uptime checks */ - snprintf(query2, BIG_BUFSIZE, + snprintf(q->query2, BIG_BUFSIZE, "SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, " "snmp_username, snmp_password, snmp_auth_protocol, " "snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, " @@ -365,7 +219,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread " AND deleted = ''", host_id); /* data query structure for reindex detection */ - snprintf(query4, BUFSIZE, + snprintf(q->query4, BUFSIZE, "SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1" " FROM poller_reindex" " WHERE host_id = %i", host_id); @@ -373,7 +227,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* multiple polling interval query for items */ if (set.active_profiles != 1) { if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, + snprintf(q->query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -385,7 +239,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread " AND rrd_next_step <= 0" "%s %s", regex_col, host_id, owner_scope, limits); } else { - snprintf(query5, BUFSIZE, + snprintf(q->query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -400,7 +254,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } } else { if (set.total_snmp_ports == 1) { - snprintf(query5, BUFSIZE, + snprintf(q->query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -411,7 +265,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread " WHERE host_id = %i" "%s %s", regex_col, host_id, owner_scope, limits); } else { - snprintf(query5, BUFSIZE, + snprintf(q->query5, BUFSIZE, "SELECT SQL_NO_CACHE action, hostname, snmp_community, " "snmp_version, snmp_username, snmp_password, " "rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, " @@ -426,27 +280,27 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } /* query to setup the next polling interval in cacti */ - snprintf(query6, BUFSIZE, + snprintf(q->query6, BUFSIZE, "UPDATE poller_item" " SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i))" " WHERE host_id = %i%s", set.poller_interval, set.poller_interval, set.poller_interval, set.poller_interval, host_id, owner_scope); /* query to add output records to the poller output table */ - snprintf(query8, BUFSIZE, + snprintf(q->query8, BUFSIZE, "INSERT INTO poller_output" " (local_data_id, rrd_name, time, output) VALUES"); /* query suffix to add rows to the poller output table */ if (set.dbonupdate == 0) { - snprintf(posuffix, BUFSIZE, + snprintf(q->posuffix, BUFSIZE, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); } else { - snprintf(posuffix, BUFSIZE, + snprintf(q->posuffix, BUFSIZE, " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); } /* number of agent's count for single polling interval */ - snprintf(query9, BUFSIZE, + snprintf(q->query9, BUFSIZE, "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" " FROM poller_item" " WHERE host_id = %i" @@ -455,7 +309,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* number of agent's count for multiple polling intervals */ if (set.active_profiles != 1) { - snprintf(query10, BUFSIZE, + snprintf(q->query10, BUFSIZE, "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" " FROM poller_item" " WHERE host_id = %i" @@ -463,7 +317,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread "%s" " GROUP BY snmp_port %s", host_id, owner_scope, limits); } else { - snprintf(query10, BUFSIZE, + snprintf(q->query10, BUFSIZE, "SELECT SQL_NO_CACHE snmp_port, count(snmp_port)" " FROM poller_item" " WHERE host_id = %i" @@ -472,13 +326,163 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } /* query to add output records to the poller output table */ - snprintf(query11, BUFSIZE, + snprintf(q->query11, BUFSIZE, "INSERT INTO poller_output_boost" " (local_data_id, rrd_name, time, output) VALUES"); - query8_len = strlen(query8); - query11_len = strlen(query11); - posuffix_len = strlen(posuffix); + q->query8_len = strlen(q->query8); + q->query11_len = strlen(q->query11); + q->posuffix_len = strlen(q->posuffix); +} + +/*! \fn void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) + * \brief core Spine function that polls a host + * \param host_id integer value for the host_id from the hosts table in Cacti + * + * This function is core to Spine. It will take a host_id and then poll it. + * + * Prior to the poll, the system will ping the host to verify that it is up. + * In addition, the system will check to see if any reindexing of data query's + * is required. + * + * If reindexing is required, the Cacti poller.php function will spawn that + * reindexing process. + * + * In the case of hosts that require reindexing because of a sysUptime + * rollback, Spine will store an unknown (NaN) value for all objects to prevent + * spikes in the graphs. + * + * With regard to snmp calls, if the host has multiple snmp agents running + * Spine will re-initialize the snmp session and poll under those new ports + * as the host poller_items table dictates. + * + */ +void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) { + poll_host_queries_t q; + char *query3 = NULL; + char *query12 = NULL; + + + char sysUptime[BUFSIZE]; + char result_string[RESULTS_BUFFER+SMALL_BUFSIZE]; + int result_length; + char temp_result[RESULTS_BUFFER]; + int errors = 0; + int *buf_errors; + int *buf_size; + char *error_string; + + int num_rows; + int assert_fail = FALSE; + int reindex_err = FALSE; + int spike_kill = FALSE; + int rows_processed = 0; + int i = 0; + int j = 0; + int k = 0; + int num_oids = 0; + size_t out_buffer; + int php_process; + + char *poll_result = NULL; + char update_sql[BIG_BUFSIZE]; + char temp_poll_result[BUFSIZE]; + char temp_arg1[BUFSIZE]; + char limits[SMALL_BUFSIZE]; + + int last_snmp_version = 0; + int last_snmp_port = 0; + char last_snmp_community[50]; + char last_snmp_username[50]; + char last_snmp_password[50]; + char last_snmp_auth_protocol[7]; + char last_snmp_priv_passphrase[200]; + char last_snmp_priv_protocol[8]; + char last_snmp_context[65]; + char last_snmp_engine_id[30]; + double poll_time = get_time_as_double(); + double thread_start = 0; + double thread_end = 0; + + /* reindex shortcuts to speed polling */ + int previous_assert_failure = FALSE; + int last_data_query_id = 0; + int perform_assert = TRUE; + int new_buffer = TRUE; + int ignore_sysinfo = TRUE; + int buf_length = 0; + + extern poller_thread_t** details; + + pool_t *local_cnn = NULL; + pool_t *remote_cnn = NULL; + + reindex_t *reindex = NULL; + host_t *host = NULL; + ping_t *ping = NULL; + name_t *name = NULL; + target_t *poller_items = NULL; + snmp_oids_t *snmp_oids = NULL; + + if (!(error_string = malloc(DBL_BUFSIZE))) { + die("ERROR: Fatal malloc error: poller.c error_string!"); + } + if (!(buf_size = malloc(sizeof(int)))) { + die("ERROR: Fatal malloc error: poller.c buf_size!"); + } + if (!(buf_errors = malloc(sizeof(int)))) { + die("ERROR: Fatal malloc error: poller.c buf_errors!"); + } + + *buf_size = 0; + *buf_errors = 0; + + MYSQL mysql; + MYSQL mysqlr; + MYSQL mysqlt; + MYSQL_RES *result; + MYSQL_ROW row; + + //db_connect(LOCAL, &mysql); + local_cnn = db_get_connection(LOCAL); + mysql = local_cnn->mysql; + + if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { + remote_cnn = db_get_connection(REMOTE); + mysqlr = remote_cnn->mysql; + } + + /* allocate host and ping structures with appropriate values */ + if (!(host = (host_t *) malloc(sizeof(host_t)))) { + die("ERROR: Fatal malloc error: poller.c host struct!"); + } + + /* set zeros */ + memset(host, 0, sizeof(host_t)); + + if (!(ping = (ping_t *) malloc(sizeof(ping_t)))) { + die("ERROR: Fatal malloc error: poller.c ping struct!"); + } + + /* set zeros */ + memset(ping, 0, sizeof(ping_t)); + + if (!(reindex = (reindex_t *) malloc(sizeof(reindex_t)))) { + die("ERROR: Fatal malloc error: poller.c reindex poll!"); + } + memset(reindex, 0, sizeof(reindex_t)); + + /* determine the SQL limits using the poller instructions */ + if (host_data_ids > 0) { + snprintf(limits, SMALL_BUFSIZE, "LIMIT %i, %i", host_data_ids * (host_thread - 1), host_data_ids); + } else { + limits[0] = '\0'; + } + + /* optional output_regex column (added in Cacti 1.3.1) */ + const char *regex_col = set.has_output_regex ? ", output_regex" : ""; + + poll_host_build_queries(&q, host_id, regex_col, limits); /* initialize the ping structure variables */ snprintf(ping->ping_status, 50, "down"); @@ -489,7 +493,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* if the host is a real host. Note host_id=0 is not host based data source */ if (host_id) { /* get data about this host */ - if ((result = db_query(&mysql, LOCAL, query2)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query2)) != 0) { num_rows = mysql_num_rows(result); if (num_rows != 1) { @@ -810,7 +814,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* do the reindex check for this host if not script based */ if ((!host->ignore_host) && (host_id)) { - if ((result = db_query(&mysql, LOCAL, query4)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query4)) != 0) { num_rows = mysql_num_rows(result); if (num_rows > 0) { @@ -1176,14 +1180,14 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread num_rows = 0; if (set.poller_interval == 0) { /* get the poller items */ - if ((result = db_query(&mysql, LOCAL, query1)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query1)) != 0) { num_rows = mysql_num_rows(result); } else { SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to Retrieve Rows due to Null Result!", host->id, host_thread)); } } else { /* get the poller items */ - if ((result = db_query(&mysql, LOCAL, query5)) != 0) { + if ((result = db_query(&mysql, LOCAL, q.query5)) != 0) { num_rows = mysql_num_rows(result); } else { SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to Retrieve Rows due to Null Result!", host->id, host_thread)); @@ -1759,7 +1763,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread memset(query3, 0, buf_length); /* append data */ - strncat(query3, query8, query8_len); + strncat(query3, q.query8, q.query8_len); out_buffer = strlen(query3); @@ -1773,7 +1777,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread memset(query12, 0, buf_length); /* append data */ - strncat(query12, query11, query11_len); + strncat(query12, q.query11, q.query11_len); } int mode; @@ -1806,7 +1810,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* if the next element to the buffer will overflow it, write to the database */ if ((out_buffer + result_length) >= MAX_MYSQL_BUF_SIZE) { /* append the suffix */ - strncat(query3, posuffix, posuffix_len); + strncat(query3, q.posuffix, q.posuffix_len); /* insert the record */ db_insert(&mysqlt, mode, query3); @@ -1814,18 +1818,18 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* re-initialize the query buffer */ memset(query3, 0, MAX_MYSQL_BUF_SIZE+RESULTS_BUFFER); - strncat(query3, query8, query8_len); + strncat(query3, q.query8, q.query8_len); /* insert the record for boost */ if (set.boost_redirect && set.boost_enabled) { /* append the suffix */ - strncat(query12, posuffix, posuffix_len); + strncat(query12, q.posuffix, q.posuffix_len); db_insert(&mysqlt, mode, query12); memset(query12, 0, MAX_MYSQL_BUF_SIZE+RESULTS_BUFFER); - strncat(query12, query11, query11_len); + strncat(query12, q.query11, q.query11_len); } /* reset the output buffer length */ @@ -1854,9 +1858,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } /* perform the last insert if there is data to process */ - if (out_buffer > strlen(query8)) { + if (out_buffer > strlen(q.query8)) { /* append the suffix */ - strncat(query3, posuffix, posuffix_len); + strncat(query3, q.posuffix, q.posuffix_len); /* insert records into database */ db_insert(&mysqlt, mode, query3); @@ -1864,7 +1868,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread /* insert the record for boost */ if (set.boost_redirect && set.boost_enabled) { /* append the suffix */ - strncat(query12, posuffix, posuffix_len); + strncat(query12, q.posuffix, q.posuffix_len); db_insert(&mysqlt, mode, query12); } @@ -1896,7 +1900,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (host_thread == host_threads && set.active_profiles != 1) { SPINE_LOG_MEDIUM(("Device[%i] HT[%i] Updating Poller Items for Next Poll", host_id, host_thread)); - db_query(&mysql, LOCAL, query6); + db_query(&mysql, LOCAL, q.query6); } /* record the polling time for the device */ @@ -1914,9 +1918,9 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread details[device_counter]->complete = TRUE; poll_time = get_time_as_double(); - query1[0] = '\0'; - snprintf(query1, BUFSIZE, "UPDATE host SET polling_time = %.3f - %.3f WHERE id = %i", poll_time, host_time_double, host_id); - db_query(&mysql, LOCAL, query1); + q.query1[0] = '\0'; + snprintf(q.query1, BUFSIZE, "UPDATE host SET polling_time = %.3f - %.3f WHERE id = %i", poll_time, host_time_double, host_id); + db_query(&mysql, LOCAL, q.query1); } diff --git a/spine.h b/spine.h index 2a05bcf3..ed061ebf 100644 --- a/spine.h +++ b/spine.h @@ -626,6 +626,28 @@ typedef struct db_connection { #include "error.h" /* Globals */ +/*! Every query poll_host() issues for one device. + * + * Grouped so the construction can be built and inspected on its own; the + * three lengths are cached because the result loop appends these fragments + * once per data source. */ +typedef struct { + char query1[BUFSIZE]; /* poller_item rows this poller owns */ + char query2[BIG_BUFSIZE]; /* host row, for the uptime checks */ + char query4[BUFSIZE]; + char query5[BUFSIZE]; + char query6[BUFSIZE]; + char query8[BUFSIZE]; + char query9[BUFSIZE]; + char query10[BUFSIZE]; + char query11[BUFSIZE]; + char posuffix[BUFSIZE]; /* upsert tail for poller_output */ + int query8_len; + int query11_len; + int posuffix_len; +} poll_host_queries_t; + +extern void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *regex_col, const char *limits); extern void poller_item_scope(char *out, size_t len, int poller_id); extern void poller_owner_scope(char *out, size_t len, int poller_id); diff --git a/tests/golden/README.md b/tests/golden/README.md index 8258a85b..0486c50b 100644 --- a/tests/golden/README.md +++ b/tests/golden/README.md @@ -1,20 +1,28 @@ # Golden capture for poll_host() query construction -`poll_host()` is the largest function in the tree and builds its SQL inline, so -the construction cannot be reached from `make check`. Before changing it, pin -what it currently emits. +`poll_host_queries.golden` holds every query `poll_host()` builds for one +device, across the inputs it branches on: `total_snmp_ports` 1 and 2, +`dbonupdate` 0 and 1, a main poller and a remote poller. -`poll_host_queries.golden` is the output of every query `poll_host()` builds, -across the input vectors it branches on: `total_snmp_ports` 1 and 2, -`dbonupdate` 0 and 1, and a main poller and a remote poller. +`test_build_queries_matches_the_golden_capture` in `tests/unit/test_linked.c` +regenerates that output from `poll_host_build_queries()` and diffs it line by +line, so `make check` fails on any change to the SQL. It reports the first +differing line and prints both sides. -To recapture, lift the query-building body out of `poller.c` into a standalone -`main()` that declares the handful of inputs it reads (`host_id`, `regex_col`, -`limits`, and `set.poller_id`, `set.total_snmp_ports`, `set.dbonupdate`, -`set.poller_interval`, `set.active_profiles`), print each buffer, and diff the -result against this file. The body compiles standalone: that closed input set is -what makes the extraction safe. +Point it at another file with `SPINE_GOLDEN=path ./tests/unit/test_linked`. -Use it the same way for the next piece of `poll_host()` that gets extracted. -Capture first, refactor second, and require the diff to be empty or to contain -only changes you can name in advance. +## Changing the queries + +A deliberate change makes the test fail. That is the point: update the fixture +in the same commit as the code, and say in the commit message what moved. Do +not regenerate it without reading the diff first, and never regenerate it to +make a red build green. + +## Extending it + +The fixture only covers what `poll_host_build_queries()` produces. The rest of +`poll_host()` still builds and mutates state inline and is not reachable from a +test. When the next piece comes out, capture what it emits before moving it, +extract, then require the diff to be empty or to contain only changes named in +advance. That is how #590 was found: the two branches of the old code disagreed +about `dbonupdate`, and the capture showed it. diff --git a/tests/unit/test_linked.c b/tests/unit/test_linked.c index 0272e0a2..73fada4b 100644 --- a/tests/unit/test_linked.c +++ b/tests/unit/test_linked.c @@ -519,6 +519,207 @@ static void test_poller_scopes_refuse_a_degenerate_buffer(void **state) { poller_owner_scope(NULL, sizeof scope, 0); } + +/* --------------------------------------------------------------------------- + * poll_host_build_queries() + * + * poll_host() is 1,600+ lines and builds its SQL inline, so none of this was + * reachable from a test. The construction now lives in its own function, and + * these pin what it emits across every input it branches on, against the + * fixture in tests/golden/poll_host_queries.golden. + * ------------------------------------------------------------------------- */ + +static void build_with(poll_host_queries_t *q, int poller_id, int ports, int dbonupdate) { + set.poller_id = poller_id; + set.total_snmp_ports = ports; + set.dbonupdate = dbonupdate; + set.poller_interval = 60; + set.active_profiles = 1; + + memset(q, 0, sizeof(*q)); + poll_host_build_queries(q, 42, ", 'RE' AS re", "LIMIT 0,100"); +} + +static void test_build_queries_scopes_the_main_poller_by_deleted(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_non_null(strstr(q.query1, " AND deleted = ''")); + assert_null(strstr(q.query1, "poller_id")); + /* the ownership filter is absent, not defaulted to some poller */ + assert_null(strstr(q.query5, "poller_id")); + assert_null(strstr(q.query9, "poller_id")); +} + +static void test_build_queries_scopes_a_remote_poller_by_owner(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 7, 1, 0); + + assert_non_null(strstr(q.query1, " AND poller_id = 7")); + assert_null(strstr(q.query1, "deleted")); + assert_non_null(strstr(q.query5, " AND poller_id = 7")); + assert_non_null(strstr(q.query9, " AND poller_id = 7")); + assert_non_null(strstr(q.query10, " AND poller_id = 7")); + + /* the host row is filtered by deleted on both, never by owner */ + assert_non_null(strstr(q.query2, " AND deleted = ''")); + assert_null(strstr(q.query2, "poller_id")); +} + +static void test_build_queries_orders_by_port_only_for_multiple_ports(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + assert_null(strstr(q.query1, "ORDER BY snmp_port")); + + build_with(&q, 0, 2, 0); + assert_non_null(strstr(q.query1, "ORDER BY snmp_port")); +} + +/* The defect the extraction exposed: the remote branch had its own copy of + this suffix and never picked up the version check. */ +static void test_build_queries_applies_dbonupdate_on_both_poller_types(void **state) { + poll_host_queries_t q; + + (void) state; + + build_with(&q, 0, 1, 0); + assert_string_equal(q.posuffix, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + build_with(&q, 0, 1, 1); + assert_string_equal(q.posuffix, " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); + + build_with(&q, 7, 1, 0); + assert_string_equal(q.posuffix, " ON DUPLICATE KEY UPDATE output=VALUES(output)"); + build_with(&q, 7, 1, 1); + assert_string_equal(q.posuffix, " AS rs ON DUPLICATE KEY UPDATE output=rs.output"); +} + +static void test_build_queries_caches_the_lengths_the_result_loop_uses(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_int_equal(q.query8_len, (int) strlen(q.query8)); + assert_int_equal(q.query11_len, (int) strlen(q.query11)); + assert_int_equal(q.posuffix_len, (int) strlen(q.posuffix)); +} + +static void test_build_queries_fills_every_buffer(void **state) { + poll_host_queries_t q; + + (void) state; + build_with(&q, 0, 1, 0); + + assert_true(strlen(q.query1) > 0); + assert_true(strlen(q.query2) > 0); + assert_true(strlen(q.query4) > 0); + assert_true(strlen(q.query5) > 0); + assert_true(strlen(q.query6) > 0); + assert_true(strlen(q.query8) > 0); + assert_true(strlen(q.query9) > 0); + assert_true(strlen(q.query10) > 0); + assert_true(strlen(q.query11) > 0); + assert_true(strlen(q.posuffix) > 0); +} + +/* The golden fixture, executed rather than documented. Regenerate it with + SPINE_WRITE_GOLDEN=1 and read the diff before committing the result. */ +static void emit_one(FILE *f, const char *tag, poll_host_queries_t *q) { + fprintf(f, "### %s query1\n%s\n", tag, q->query1); + fprintf(f, "### %s query2\n%s\n", tag, q->query2); + fprintf(f, "### %s query4\n%s\n", tag, q->query4); + fprintf(f, "### %s query5\n%s\n", tag, q->query5); + fprintf(f, "### %s query6\n%s\n", tag, q->query6); + fprintf(f, "### %s query8\n%s\n", tag, q->query8); + fprintf(f, "### %s query9\n%s\n", tag, q->query9); + fprintf(f, "### %s query10\n%s\n", tag, q->query10); + fprintf(f, "### %s posuffix\n%s\n", tag, q->posuffix); +} + +static void write_all(FILE *f) { + poll_host_queries_t q; + int ports[2] = {1, 2}; + int onupd[2] = {0, 1}; + int pid[2] = {3, 7}; + int i, j; + + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + build_with(&q, 0, ports[i], onupd[j]); + fprintf(f, "== MAIN ports=%d onupd=%d ==\n", ports[i], onupd[j]); + emit_one(f, "main", &q); + } + } + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + build_with(&q, pid[j], ports[i], onupd[j]); + fprintf(f, "== REMOTE ports=%d onupd=%d pid=%d ==\n", ports[i], onupd[j], pid[j]); + emit_one(f, "remote", &q); + } + } +} + +static void test_build_queries_matches_the_golden_capture(void **state) { + const char *path = getenv("SPINE_GOLDEN"); + char actual[] = "/tmp/spine_golden_actual.XXXXXX"; + FILE *f; + FILE *g; + int fd; + int line = 0; + char a[BIG_BUFSIZE]; + char b[BIG_BUFSIZE]; + + (void) state; + + if (path == NULL) { + path = "tests/golden/poll_host_queries.golden"; + } + + g = fopen(path, "r"); + if (g == NULL) { + print_message("golden fixture %s not readable, skipping\n", path); + return; + } + + fd = mkstemp(actual); + assert_true(fd >= 0); + f = fdopen(fd, "w+"); + assert_non_null(f); + + write_all(f); + fflush(f); + rewind(f); + + while (fgets(a, sizeof(a), g) != NULL) { + line++; + if (fgets(b, sizeof(b), f) == NULL) { + fclose(g); + fclose(f); + unlink(actual); + fail_msg("golden has more lines than produced, first missing at %d", line); + } + if (strcmp(a, b) != 0) { + print_message("line %d\n golden: %s actual: %s", line, a, b); + fclose(g); + fclose(f); + unlink(actual); + fail_msg("query construction diverged from the golden capture at line %d", line); + } + } + + assert_null(fgets(b, sizeof(b), f)); + + fclose(g); + fclose(f); + unlink(actual); +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_strncopy_truncates_within_the_buffer), @@ -563,6 +764,13 @@ int main(void) { cmocka_unit_test(test_poller_owner_scope_is_empty_on_the_main_poller), cmocka_unit_test(test_poller_owner_scope_names_the_remote_poller), cmocka_unit_test(test_poller_scopes_refuse_a_degenerate_buffer), + cmocka_unit_test(test_build_queries_scopes_the_main_poller_by_deleted), + cmocka_unit_test(test_build_queries_scopes_a_remote_poller_by_owner), + cmocka_unit_test(test_build_queries_orders_by_port_only_for_multiple_ports), + cmocka_unit_test(test_build_queries_applies_dbonupdate_on_both_poller_types), + cmocka_unit_test(test_build_queries_caches_the_lengths_the_result_loop_uses), + cmocka_unit_test(test_build_queries_fills_every_buffer), + cmocka_unit_test(test_build_queries_matches_the_golden_capture), }; return cmocka_run_group_tests(tests, NULL, NULL); From 991c1f144e5d11fdf993e92b755f630faa9589c8 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 2 Sep 2026 17:41:37 -0700 Subject: [PATCH 5/5] fix: end the MySQL thread on every poll_host exit poll_host() leaves through three places and each spelled its teardown out again. The copies were not in the same order and did not hold the same steps: the device-row-missing path never called mysql_thread_end(), so a device deleted mid-cycle leaked the client library's thread-local state once per cycle. Closes #594. The two early exits now share poll_host_release(). The normal exit uses error_string after the point where that helper frees it, so it shares only poll_host_release_connections() and keeps its own ordering. Signed-off-by: Thomas Vincent --- poller.c | 101 +++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/poller.c b/poller.c index e9784ad6..66d8e91a 100644 --- a/poller.c +++ b/poller.c @@ -335,6 +335,47 @@ void poll_host_build_queries(poll_host_queries_t *q, int host_id, const char *re q->posuffix_len = strlen(q->posuffix); } +/*! \fn static void poll_host_release(host_t **host, reindex_t **reindex, ping_t **ping, char **error_string, int **buf_size, int **buf_errors, pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) + * \brief release everything poll_host() owns, on every exit path + * + * poll_host() leaves through three places and each used to spell this out + * again. The copies were not in the same order and did not contain the same + * steps: the device-row-missing path never called mysql_thread_end(), which + * leaks the client library's thread-local state once per affected device per + * cycle on a thread-per-device poller. See #594. + */ +static void poll_host_release_connections(pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) { + if (local_cnn != NULL) { + db_release_connection(LOCAL, local_cnn->id); + } else { + SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); + } + + if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { + if (remote_cnn != NULL) { + db_release_connection(REMOTE, remote_cnn->id); + } else { + SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); + } + } +} + +static void poll_host_release(host_t **host, reindex_t **reindex, ping_t **ping, + char **error_string, int **buf_size, int **buf_errors, + pool_t *local_cnn, pool_t *remote_cnn, int host_id, int host_thread) { + + poll_host_release_connections(local_cnn, remote_cnn, host_id, host_thread); + + SPINE_FREE(*host); + SPINE_FREE(*reindex); + SPINE_FREE(*ping); + SPINE_FREE(*error_string); + SPINE_FREE(*buf_size); + SPINE_FREE(*buf_errors); + + mysql_thread_end(); +} + /*! \fn void poll_host(int device_counter, int host_id, int host_thread, int host_threads, int host_data_ids, char *host_time, int *host_errors, double host_time_double) * \brief core Spine function that polls a host * \param host_id integer value for the host_id from the hosts table in Cacti @@ -499,26 +540,8 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread if (num_rows != 1) { db_free_result(result); - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } - - SPINE_FREE(host); - SPINE_FREE(reindex); - SPINE_FREE(ping); - SPINE_FREE(error_string); - SPINE_FREE(buf_size); - SPINE_FREE(buf_errors); + poll_host_release(&host, &reindex, &ping, &error_string, &buf_size, &buf_errors, + local_cnn, remote_cnn, host_id, host_thread); return; } @@ -786,28 +809,8 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread } if (set.ping_only) { - SPINE_FREE(host); - SPINE_FREE(reindex); - SPINE_FREE(ping); - SPINE_FREE(error_string); - SPINE_FREE(buf_size); - SPINE_FREE(buf_errors); - - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } - - mysql_thread_end(); + poll_host_release(&host, &reindex, &ping, &error_string, &buf_size, &buf_errors, + local_cnn, remote_cnn, host_id, host_thread); return; } @@ -1945,19 +1948,7 @@ void poll_host(int device_counter, int host_id, int host_thread, int host_thread thread_mutex_unlock(LOCK_THDET); - if (local_cnn != NULL) { - db_release_connection(LOCAL, local_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", host_id, host_thread)); - } - - if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) { - if (remote_cnn != NULL) { - db_release_connection(REMOTE, remote_cnn->id); - } else { - SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", host_id, host_thread)); - } - } + poll_host_release_connections(local_cnn, remote_cnn, host_id, host_thread); mysql_thread_end();