summaryrefslogtreecommitdiffstats
path: root/src/stats-file.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-03 05:11:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-03 05:11:10 +0000
commitcff6d757e3ba609c08ef2aaa00f07e53551e5bf6 (patch)
tree08c4fc3255483ad397d712edb4214ded49149fd9 /src/stats-file.c
parentAdding upstream version 2.9.7. (diff)
downloadhaproxy-cff6d757e3ba609c08ef2aaa00f07e53551e5bf6.tar.xz
haproxy-cff6d757e3ba609c08ef2aaa00f07e53551e5bf6.zip
Adding upstream version 3.0.0.upstream/3.0.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/stats-file.c')
-rw-r--r--src/stats-file.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/stats-file.c b/src/stats-file.c
new file mode 100644
index 0000000..1a77e31
--- /dev/null
+++ b/src/stats-file.c
@@ -0,0 +1,426 @@
+#include <haproxy/stats-file.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <import/ebmbtree.h>
+#include <import/ebsttree.h>
+#include <import/ist.h>
+#include <haproxy/api.h>
+#include <haproxy/buf.h>
+#include <haproxy/chunk.h>
+#include <haproxy/clock.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/guid.h>
+#include <haproxy/intops.h>
+#include <haproxy/list.h>
+#include <haproxy/listener-t.h>
+#include <haproxy/obj_type.h>
+#include <haproxy/proxy-t.h>
+#include <haproxy/server-t.h>
+#include <haproxy/stats.h>
+#include <haproxy/time.h>
+
+/* Dump all fields from <stats> into <out> for stats-file. */
+int stats_dump_fields_file(struct buffer *out,
+ const struct field *line, size_t stats_count,
+ struct show_stat_ctx *ctx)
+{
+ struct guid_node *guid;
+ struct listener *l;
+ int i;
+
+ switch (ctx->px_st) {
+ case STAT_PX_ST_FE:
+ case STAT_PX_ST_BE:
+ guid = &__objt_proxy(ctx->obj1)->guid;
+ break;
+
+ case STAT_PX_ST_LI:
+ l = LIST_ELEM(ctx->obj2, struct listener *, by_fe);
+ guid = &l->guid;
+ break;
+
+ case STAT_PX_ST_SV:
+ guid = &__objt_server(ctx->obj2)->guid;
+ break;
+
+ default:
+ ABORT_NOW();
+ return 1;
+ }
+
+ /* Skip objects without GUID. */
+ if (!guid->node.key)
+ return 1;
+
+ chunk_appendf(out, "%s,", (char *)guid->node.key);
+
+ for (i = 0; i < stats_count; ++i) {
+ /* Empty field for stats-file is used to skip its output,
+ * including any separator.
+ */
+ if (field_format(line, i) == FF_EMPTY)
+ continue;
+
+ if (!stats_emit_raw_data_field(out, &line[i]))
+ return 0;
+ if (!chunk_strcat(out, ","))
+ return 0;
+ }
+
+ chunk_strcat(out, "\n");
+ return 1;
+}
+
+void stats_dump_file_header(int type, struct buffer *out)
+{
+ const struct stat_col *col;
+ int i;
+
+ /* Caller must specified ither FE or BE. */
+ BUG_ON(!(type & ((1 << STATS_TYPE_FE) | (1 << STATS_TYPE_BE))));
+
+ if (type & (1 << STATS_TYPE_FE)) {
+ chunk_strcat(out, "#fe guid,");
+ for (i = 0; i < ST_I_PX_MAX; ++i) {
+ col = &stat_cols_px[i];
+ if (stcol_is_generic(col) &&
+ col->cap & (STATS_PX_CAP_FE|STATS_PX_CAP_LI)) {
+ chunk_appendf(out, "%s,", col->name);
+ }
+ }
+ }
+ else {
+ chunk_appendf(out, "#be guid,");
+ for (i = 0; i < ST_I_PX_MAX; ++i) {
+ col = &stat_cols_px[i];
+ if (stcol_is_generic(col) &&
+ col->cap & (STATS_PX_CAP_BE|STATS_PX_CAP_SRV)) {
+ chunk_appendf(out, "%s,", col->name);
+ }
+ }
+ }
+
+ chunk_strcat(out, "\n");
+}
+
+/* Parse an identified header line <header> starting with '#' character.
+ *
+ * If the section is recognized, <domain> will point to the current stats-file
+ * scope. <cols> will be filled as a matrix to identify each stat_col position
+ * using <st_tree> as prefilled proxy stats columns. If stats-file section is
+ * unknown, only <domain> will be set to STFILE_DOMAIN_UNSET.
+ *
+ * Returns 0 on success. On fatal error, non-zero is returned and parsing should
+ * be interrupted.
+ */
+static int parse_header_line(struct ist header, struct eb_root *st_tree,
+ enum stfile_domain *domain,
+ const struct stat_col *cols[])
+{
+ enum stfile_domain dom = STFILE_DOMAIN_UNSET;
+ struct ist token;
+ char last;
+ int i;
+
+ header = iststrip(header);
+ last = istptr(header)[istlen(header) - 1];
+ token = istsplit(&header, ' ');
+
+ /* A header line is considered valid if:
+ * - a space delimiter is found and first token is several chars
+ * - last line character must be a comma separator
+ */
+ if (!istlen(header) || istlen(token) == 1 || last != ',')
+ goto err;
+
+ if (isteq(token, ist("#fe")))
+ dom = STFILE_DOMAIN_PX_FE;
+ else if (isteq(token, ist("#be")))
+ dom = STFILE_DOMAIN_PX_BE;
+
+ /* Remove 'guid' field. */
+ token = istsplit(&header, ',');
+ if (!isteq(token, ist("guid"))) {
+ /* Fatal error if FE/BE domain without guid token. */
+ if (dom == STFILE_DOMAIN_PX_FE || dom == STFILE_DOMAIN_PX_BE)
+ goto err;
+ }
+
+ /* Unknown domain. Following lines should be ignored until next header. */
+ if (dom == STFILE_DOMAIN_UNSET)
+ return 0;
+
+ /* Generate matrix of stats column into cols[]. */
+ memset(cols, 0, sizeof(void *) * STAT_FILE_MAX_COL_COUNT);
+
+ i = 0;
+ while (istlen(header) && i < STAT_FILE_MAX_COL_COUNT) {
+ struct stcol_node *col_node;
+ const struct stat_col *col;
+ struct ebmb_node *node;
+
+ /* Lookup column by its name into <st_tree>. */
+ token = istsplit(&header, ',');
+ node = ebst_lookup(st_tree, ist0(token));
+ if (!node) {
+ ++i;
+ continue;
+ }
+
+ col_node = ebmb_entry(node, struct stcol_node, name);
+ col = col_node->col;
+
+ /* Ignore column if its cap is not valid with current stats-file section. */
+ if ((dom == STFILE_DOMAIN_PX_FE &&
+ !(col->cap & (STATS_PX_CAP_FE|STATS_PX_CAP_LI))) ||
+ (dom == STFILE_DOMAIN_PX_BE &&
+ !(col->cap & (STATS_PX_CAP_BE|STATS_PX_CAP_SRV)))) {
+ ++i;
+ continue;
+ }
+
+ cols[i] = col;
+ ++i;
+ }
+
+ *domain = dom;
+ return 0;
+
+ err:
+ *domain = STFILE_DOMAIN_UNSET;
+ return 1;
+}
+
+/* Preload an individual counter instance stored at <counter> with <token>
+ * value> for the <col> stat column.
+ *
+ * Returns 0 on success else non-zero if counter was not updated.
+ */
+static int load_ctr(const struct stat_col *col, const struct ist token,
+ void* counter)
+{
+ const enum field_nature fn = stcol_nature(col);
+ const enum field_format ff = stcol_format(col);
+ const char *ptr = istptr(token);
+ struct field value;
+
+ switch (ff) {
+ case FF_U64:
+ value.u.u64 = read_uint64(&ptr, istend(token));
+ break;
+
+ case FF_S32:
+ case FF_U32:
+ value.u.u32 = read_uint(&ptr, istend(token));
+ break;
+
+ default:
+ /* Unsupported field nature. */
+ return 1;
+ }
+
+ /* Do not load value if non numeric characters present. */
+ if (ptr != istend(token))
+ return 1;
+
+ if (fn == FN_COUNTER && ff == FF_U64) {
+ *(uint64_t *)counter = value.u.u64;
+ }
+ else if (fn == FN_RATE && ff == FF_U32) {
+ preload_freq_ctr(counter, value.u.u32);
+ }
+ else if (fn == FN_AGE && (ff == FF_U32 || ff == FF_S32)) {
+ *(uint32_t *)counter = ns_to_sec(now_ns) - value.u.u32;
+ }
+ else {
+ /* Unsupported field format/nature combination. */
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Parse a non header stats-file line <line>. Specify current parsing <domain>
+ * and <cols> stats column matrix derived from the last header line.
+ *
+ * Returns 0 on success else non-zero.
+ */
+static int parse_stat_line(struct ist line,
+ enum stfile_domain domain,
+ const struct stat_col *cols[])
+{
+ struct guid_node *node;
+ struct listener *li;
+ struct server *srv;
+ struct proxy *px;
+ struct ist token;
+ char *base_off;
+ char *guid;
+ int i, off;
+
+ token = istsplit(&line, ',');
+ guid = ist0(token);
+ if (!guid_is_valid_fmt(guid, NULL))
+ goto err;
+
+ node = guid_lookup(guid);
+ if (!node) {
+ /* Silently ignored unknown GUID. */
+ return 0;
+ }
+
+ switch (obj_type(node->obj_type)) {
+ case OBJ_TYPE_PROXY:
+ px = __objt_proxy(node->obj_type);
+
+ if (domain == STFILE_DOMAIN_PX_FE) {
+ if (!(px->cap & PR_CAP_FE))
+ goto err;
+ base_off = (char *)&px->fe_counters;
+ off = 0;
+ }
+ else if (domain == STFILE_DOMAIN_PX_BE) {
+ if (!(px->cap & PR_CAP_BE))
+ goto err;
+ base_off = (char *)&px->be_counters;
+ off = 1;
+ }
+ else {
+ goto err;
+ }
+
+ break;
+
+ case OBJ_TYPE_LISTENER:
+ if (domain != STFILE_DOMAIN_PX_FE)
+ goto err;
+
+ li = __objt_listener(node->obj_type);
+ /* Listeners counters are not allocated if 'option socket-stats' unset. */
+ if (!li->counters)
+ return 0;
+
+ base_off = (char *)li->counters;
+ off = 0;
+ break;
+
+ case OBJ_TYPE_SERVER:
+ if (domain != STFILE_DOMAIN_PX_BE)
+ goto err;
+
+ srv = __objt_server(node->obj_type);
+ base_off = (char *)&srv->counters;
+ off = 1;
+ break;
+
+ default:
+ goto err;
+ }
+
+ i = 0;
+ while (istlen(line) && i < STAT_FILE_MAX_COL_COUNT) {
+ const struct stat_col *col = cols[i++];
+
+ token = istsplit(&line, ',');
+ if (!istlen(token))
+ continue;
+
+ if (!col)
+ continue;
+
+ load_ctr(col, token, base_off + col->metric.offset[off]);
+ }
+
+ return 0;
+
+ err:
+ return 1;
+}
+
+/* Parse a stats-file and preload haproxy internal counters. */
+void apply_stats_file(void)
+{
+ const struct stat_col *cols[STAT_FILE_MAX_COL_COUNT];
+ struct eb_root st_tree = EB_ROOT;
+ enum stfile_domain domain;
+ int valid_format = 0;
+ FILE *file;
+ struct ist istline;
+ char *line = NULL;
+ int linenum;
+
+ if (!global.stats_file)
+ return;
+
+ file = fopen(global.stats_file, "r");
+ if (!file) {
+ ha_warning("config: Can't load stats file: cannot open file.\n");
+ return;
+ }
+
+ /* Generate stat columns map indexed by name. */
+ if (generate_stat_tree(&st_tree, stat_cols_px)) {
+ ha_warning("config: Can't load stats file: not enough memory.\n");
+ goto out;
+ }
+
+ line = malloc(sizeof(char) * LINESIZE);
+ if (!line) {
+ ha_warning("config: Can't load stats file: line alloc error.\n");
+ goto out;
+ }
+
+ linenum = 0;
+ domain = STFILE_DOMAIN_UNSET;
+ while (1) {
+ if (!fgets(line, LINESIZE, file))
+ break;
+
+ ++linenum;
+ istline = iststrip(ist(line));
+ if (!istlen(istline))
+ continue;
+
+ if (*istptr(istline) == '#') {
+ if (parse_header_line(istline, &st_tree, &domain, cols)) {
+ if (!valid_format) {
+ ha_warning("config: Invalid stats-file format.\n");
+ break;
+ }
+
+ ha_warning("config: Ignored stats-file header line '%d'.\n", linenum);
+ }
+
+ valid_format = 1;
+ }
+ else if (domain != STFILE_DOMAIN_UNSET) {
+ if (parse_stat_line(istline, domain, cols))
+ ha_warning("config: Ignored stats-file line %d.\n", linenum);
+ }
+ else {
+ /* Stop parsing if first line is not a valid header.
+ * Allows to immediately stop reading garbage file.
+ */
+ if (!valid_format) {
+ ha_warning("config: Invalid stats-file format.\n");
+ break;
+ }
+ }
+ }
+
+ out:
+ while (!eb_is_empty(&st_tree)) {
+ struct ebmb_node *node = ebmb_first(&st_tree);
+ struct stcol_node *snode = ebmb_entry(node, struct stcol_node, name);
+
+ ebmb_delete(node);
+ ha_free(&snode);
+ }
+
+ ha_free(&line);
+ fclose(file);
+}