// A column is exactly 16 characters wide
#define COLUMN "%16s"
+#define PERCENTAGE "%%13.2lf%%%%"
#define INTEGER "%%16.0lf"
#define LARGE_INTEGER "%%14.0lf %%s"
#define FLOAT "%%14.2lf"
#define DRAW_LINE3(args, field, color, ...) DRAW(args, "LINE3", field, color, __VA_ARGS__)
#define DRAW_LINE3_WITH_LABEL(args, field, color, label, ...) DRAW_WITH_LABEL(args, "LINE3", field, color, label, __VA_ARGS__)
+// Macros to draw the background of an area
+#define DRAW_AREA_BACKGROUND(args, field, color, ...) \
+ DRAW(args, "AREA", field, COLOR_WITH_ALPHA(color, OPACITY_AREA), __VA_ARGS__)
+
+// Macros to draw the area's outline
+#define DRAW_AREA_OUTLINE_WITH_LABEL DRAW_LINE1_WITH_LABEL
+
// Areas are being drawn with an outline
#define DRAW_AREA_WITH_LABEL(args, field, color, label, ...) \
do { \
- DRAW(args, "AREA", field, COLOR_WITH_ALPHA(color, OPACITY_AREA)); \
- DRAW_LINE1_WITH_LABEL(args, field, color, label, __VA_ARGS__); \
+ DRAW_AREA_BACKGROUND(args, field, color); \
+ DRAW_AREA_OUTLINE_WITH_LABEL(args, field, color, label, __VA_ARGS__); \
} while(0)
+// Modifiers
+#define STACK ":STACK"
+
// Modifiers for lines
#define DASHES ":dashes"
#define SKIPSCALE ":skipscale"
// Add something to the legend of the graph
#define PRINT(args, field, ...) SCRIPT(args, "GPRINT:" field ":" __VA_ARGS__)
+#define PRINT_EMPTY_LINE(args) SCRIPT(args, "COMMENT: \\n")
#define PRINT_HEADER(args, label, ...) SCRIPT(args, "COMMENT:" COLUMN __VA_ARGS__, label)
+#define PRINT_LABEL(args, label, ...) SCRIPT(args, "COMMENT: %-31s" __VA_ARGS__, label)
#define PRINT_EMPTY_LABEL(args, ...) SCRIPT(args, "COMMENT: " __VA_ARGS__)
#define PRINT_NOTHING(args, ...) SCRIPT(args, "COMMENT: " __VA_ARGS__)
+#define PRINT_PERCENTAGE(args, field, ...) PRINT(args, field, PERCENTAGE __VA_ARGS__)
#define PRINT_INTEGER(args, field, ...) PRINT(args, field, INTEGER __VA_ARGS__)
#define PRINT_LARGE_INTEGER(args, field, ...) PRINT(args, field, LARGE_INTEGER __VA_ARGS__)
#define PRINT_FLOAT(args, field, ...) PRINT(args, field, FLOAT __VA_ARGS__)
PRINT_HEADER(args, header4, EOL); \
} while (0)
+// Handles for fields
+#define FIELD_CURRENT(field) field "_cur"
+#define FIELD_AVERAGE(field) field "_avg"
+#define FIELD_MINIMUM(field) field "_min"
+#define FIELD_MAXIMUM(field) field "_max"
+#define FIELD_PERCENT(field) field "_p"
+
+#define VALUE_CURRENT(args, field) SCRIPT(args, "VDEF:" FIELD_CURRENT(field) "=" field ",LAST")
+#define VALUE_AVERAGE(args, field) SCRIPT(args, "VDEF:" FIELD_AVERAGE(field) "=" field ",AVERAGE")
+#define VALUE_MINIMUM(args, field) SCRIPT(args, "VDEF:" FIELD_MINIMUM(field) "=" field ",MINIMUM")
+#define VALUE_MAXIMUM(args, field) SCRIPT(args, "VDEF:" FIELD_MAXIMUM(field) "=" field ",MAXIMUM")
+
+// Computes all values
+#define VALUE_ALL(args, field) \
+ do { \
+ VALUE_CURRENT(args, field); \
+ VALUE_AVERAGE(args, field); \
+ VALUE_MINIMUM(args, field); \
+ VALUE_MAXIMUM(args, field); \
+ } while (0)
+
+#define COMPUTE_SUM(args, sum, summand1, summand2) \
+ do { \
+ SCRIPT(args, "CDEF:" sum "=" summand1 "," summand2 ",+"); \
+ VALUE_ALL(args, sum); \
+ } while (0)
+
+#define COMPUTE_PERCENTAGE(args, field, total) \
+ do { \
+ SCRIPT(args, "CDEF:" FIELD_PERCENT(field) "=100," field ",*," total ",/"); \
+ VALUE_ALL(args, FIELD_PERCENT(field)); \
+ } while (0)
+
+
#endif /* COLLECTY_GRAPHS_GRAPH_H */
--- /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 "graph.h"
+#include "processor.h"
+
+// Set some colors
+#define COLOR_USER LIGHT_GREEN
+#define COLOR_NICE BLUE
+#define COLOR_SYS RED
+#define COLOR_WAIT DEEP_PURPLE
+#define COLOR_IRQ ORANGE
+#define COLOR_SOFTIRQ YELLOW
+#define COLOR_STEAL LIGHT_BLUE
+#define COLOR_GUEST PINK
+#define COLOR_GUEST_NICE PINK
+#define COLOR_IDLE LIGHT_GREY
+
+static int processor_title(collecty_ctx* ctx, collecty_graph* graph,
+ const char* object, char** title) {
+ return collecty_format_title(title, "%s", _("Processor Usage"));
+}
+
+static int processor_vlabel(collecty_ctx* ctx, collecty_graph* graph,
+ const char* object, char** title) {
+ return collecty_format_title(title, "%s", _("Percent"));
+}
+
+
+static int processor_render(collecty_ctx* ctx,
+ collecty_graph* graph, collecty_args* args, const char* object) {
+ int r;
+
+ // This requires the loadavg source
+ r = collecty_graph_require_source(graph, args, "processor", object);
+ if (r < 0)
+ return r;
+
+ // Add up all used cycles
+ r = collecty_args_push(args,
+ "CDEF:usage=user,nice,+,sys,+,wait,+,irq,+,softirq,+,steal,+,guest,+,guest_nice,+");
+ if (r < 0)
+ return r;
+
+ // Add idle to get the total number of cycles
+ COMPUTE_SUM(args, "total", "usage", "idle");
+
+ // Compute usage in percent
+ COMPUTE_PERCENTAGE(args, "usage", "total");
+ COMPUTE_PERCENTAGE(args, "user", "total");
+ COMPUTE_PERCENTAGE(args, "nice", "total");
+ COMPUTE_PERCENTAGE(args, "sys", "total");
+ COMPUTE_PERCENTAGE(args, "wait", "total");
+ COMPUTE_PERCENTAGE(args, "irq", "total");
+ COMPUTE_PERCENTAGE(args, "softirq", "total");
+ COMPUTE_PERCENTAGE(args, "steal", "total");
+ COMPUTE_PERCENTAGE(args, "guest", "total");
+ COMPUTE_PERCENTAGE(args, "guest_nice", "total");
+
+ // Header
+ PRINT_HEADER4(args, _("Current"), _("Average"), _("Minimum"), _("Maximum"));
+
+ // Show the total usage
+ PRINT_LABEL(args, _("Total"));
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("usage")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("usage")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("usage")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("usage")), EOL);
+
+ PRINT_EMPTY_LINE(args);
+
+ // Draw the stacked background first
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("user"), COLOR_USER);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("nice"), COLOR_NICE, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("sys"), COLOR_SYS, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("wait"), COLOR_WAIT, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("irq"), COLOR_IRQ, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("softirq"), COLOR_SOFTIRQ, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("steal"), COLOR_STEAL, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("guest"), COLOR_GUEST, STACK);
+ DRAW_AREA_BACKGROUND(args, FIELD_PERCENT("guest_nice"), COLOR_GUEST_NICE, STACK);
+
+ // Draw the area outlines afterwards
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("user"), COLOR_USER, _("User"));
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("user")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("user")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("user")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("user")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("nice"), COLOR_NICE, _("Nice"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("nice")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("nice")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("nice")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("nice")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("sys"), COLOR_SYS, _("Sys"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("sys")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("sys")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("sys")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("sys")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("wait"), COLOR_WAIT, _("Wait"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("wait")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("wait")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("wait")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("wait")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("irq"), COLOR_IRQ, _("Interrupt"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("irq")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("irq")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("irq")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("irq")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("softirq"), COLOR_SOFTIRQ, _("Soft Interrupt"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("softirq")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("softirq")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("softirq")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("softirq")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("steal"), COLOR_STEAL, _("Steal"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("steal")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("steal")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("steal")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("steal")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("guest"), COLOR_GUEST, _("Guest"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("guest")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("guest")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("guest")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("guest")), EOL);
+
+ DRAW_AREA_OUTLINE_WITH_LABEL(args,
+ FIELD_PERCENT("guest_nice"), COLOR_GUEST_NICE, _("Guest Nice"), STACK);
+ PRINT_PERCENTAGE(args, FIELD_CURRENT(FIELD_PERCENT("guest_nice")));
+ PRINT_PERCENTAGE(args, FIELD_AVERAGE(FIELD_PERCENT("guest_nice")));
+ PRINT_PERCENTAGE(args, FIELD_MINIMUM(FIELD_PERCENT("guest_nice")));
+ PRINT_PERCENTAGE(args, FIELD_MAXIMUM(FIELD_PERCENT("guest_nice")), EOL);
+
+ return 0;
+}
+
+const collecty_graph_impl processor_graph = {
+ .name = "Processor",
+ .render = processor_render,
+ .title = processor_title,
+ .vlabel = processor_vlabel,
+
+ // Limits
+ .lower_limit = 0,
+ .upper_limit = 100,
+};