#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Dump all fields from into 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
starting with '#' character. * * If the section is recognized, will point to the current stats-file * scope. will be filled as a matrix to identify each stat_col position * using as prefilled proxy stats columns. If stats-file section is * unknown, only 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 . */ 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 with * value> for the 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 . Specify current parsing * and 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); }