--- /dev/null
+/*#############################################################################
+# #
+# collecty - A system statistics collection daemon for IPFire #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+
+#include "../command.h"
+#include "../ctx.h"
+#include "../source.h"
+#include "../util.h"
+#include "unbound.h"
+
+typedef struct collecty_unbound_stats {
+ unsigned long queries;
+ unsigned long cachehits;
+ unsigned long cachemiss;
+ unsigned long prefetch;
+ unsigned long rec_replies;
+ double rec_time_avg;
+ unsigned long rec_time_median;
+} collecty_unbound_stats;
+
+static int unbound_parse(const char* line, const size_t length, void* data) {
+ collecty_unbound_stats* stats = data;
+ int r;
+
+ // Parse queries
+ r = sscanf(line, "total.num.queries=%lu", &stats->queries);
+ if (r == 1)
+ return 0;
+
+ // Parse cachehits
+ r = sscanf(line, "total.num.cachehits=%lu", &stats->cachehits);
+ if (r == 1)
+ return 0;
+
+ // Parse cachemiss
+ r = sscanf(line, "total.num.cachemiss=%lu", &stats->cachemiss);
+ if (r == 1)
+ return 0;
+
+ // Parse prefetch
+ r = sscanf(line, "total.num.prefetch=%lu", &stats->prefetch);
+ if (r == 1)
+ return 0;
+
+ // Parse rec_replies
+ r = sscanf(line, "total.num.recursivereplies=%lu", &stats->rec_replies);
+ if (r == 1)
+ return 0;
+
+ // Parse rec_time_avg
+ r = sscanf(line, "total.recursion.time.avg=%lf", &stats->rec_time_avg);
+ if (r == 1)
+ return 0;
+
+ // Parse rec_time_median
+ r = sscanf(line, "total.recursion.time.median=%lu", &stats->rec_time_median);
+ if (r == 1)
+ return 0;
+
+ return 0;
+}
+
+static int unbound_on_success(collecty_ctx* ctx,
+ int rc, const char* output, const size_t length, void* data) {
+ collecty_source* source = data;
+ int r;
+
+ // Collect stats
+ collecty_unbound_stats stats = {};
+
+ // Parse the output
+ r = collecty_fwalk_buffer(output, length, unbound_parse, &stats);
+ if (r < 0)
+ return r;
+
+ // Submit values
+ return collecty_source_submit(source, NULL, "%lu:%lu:%lu:%lu:%lu:%lf:%lu",
+ stats.queries, stats.cachehits, stats.cachemiss, stats.prefetch,
+ stats.rec_replies, stats.rec_time_avg, stats.rec_time_median);
+}
+
+static int unbound_collect(collecty_ctx* ctx, collecty_source* source) {
+ collecty_command* command = NULL;
+ int r;
+
+ // Run unbound-control to fetch stats
+ const char* argv[] = { "unbound-control", "stats_noreset", NULL };
+
+ // Create a new command
+ r = collecty_source_create_command(source, &command);
+ if (r < 0)
+ goto ERROR;
+
+ // Register the success callback
+ collecty_command_on_success(command, unbound_on_success, source);
+
+ // Execute the command
+ r = collecty_command_execute(command, argv);
+
+ERROR:
+ if (command)
+ collecty_command_unref(command);
+
+ return r;
+}
+
+const collecty_source_impl unbound_source = {
+ .name = "unbound",
+
+ // RRD Data Sources
+ .rrd_dss = {
+ { "queries", "DERIVE", 0, -1, },
+ { "cachehits", "DERIVE", 0, -1, },
+ { "cachemiss", "DERIVE", 0, -1, },
+ { "prefetch", "DERIVE", 0, -1, },
+ { "rec_replies", "DERIVE", 0, -1, },
+ { "rec_time_avg", "GAUGE", 0, -1, },
+ { "rec_time_median", "GAUGE", 0, -1, },
+ { NULL },
+ },
+
+ // Methods
+ .collect = unbound_collect,
+};
--- /dev/null
+/*#############################################################################
+# #
+# collecty - A system statistics collection daemon for IPFire #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef COLLECTY_SOURCE_UNBOUND_H
+#define COLLECTY_SOURCE_UNBOUND_H
+
+#include "../source.h"
+
+extern const collecty_source_impl unbound_source;
+
+#endif /* COLLECTY_SOURCE_UNBOUND_H */