diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h index f3e357ecf..04eae5df0 100644 --- a/include/haproxy/defaults.h +++ b/include/haproxy/defaults.h @@ -366,6 +366,13 @@ #define STATS_VERSION_STRING " version " HAPROXY_VERSION ", released " HAPROXY_DATE #endif +/* specifies the default max number of object per thread group that the shm stats file + * will be able to handle + */ +#ifndef SHM_STATS_FILE_MAX_OBJECTS +#define SHM_STATS_FILE_MAX_OBJECTS 2000 +#endif + /* This is the default statistics URI */ #ifdef CONFIG_STATS_DEFAULT_URI #define STATS_DEFAULT_URI CONFIG_STATS_DEFAULT_URI diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h index 647f1a32c..710e3e421 100644 --- a/include/haproxy/global-t.h +++ b/include/haproxy/global-t.h @@ -167,6 +167,7 @@ struct global { char *server_state_base; /* path to a directory where server state files can be found */ char *server_state_file; /* path to the file where server states are loaded from */ char *stats_file; /* path to stats-file */ + char *shm_stats_file; /* path to shm-stats-file */ unsigned char cluster_secret[16]; /* 128 bits of an SHA1 digest of a secret defined as ASCII string */ struct { int maxpollevents; /* max number of poll events at once */ diff --git a/include/haproxy/stats-file-t.h b/include/haproxy/stats-file-t.h index 03813e001..ba0f77cb6 100644 --- a/include/haproxy/stats-file-t.h +++ b/include/haproxy/stats-file-t.h @@ -1,6 +1,11 @@ #ifndef _HAPROXY_STATS_FILE_T_H #define _HAPROXY_STATS_FILE_T_H +#include +#include +#include +#include + /* Sections present in stats-file separated by header lines. */ enum stfile_domain { STFILE_DOMAIN_UNSET = 0, @@ -9,4 +14,21 @@ enum stfile_domain { STFILE_DOMAIN_PX_BE, /* #be headers */ }; +#define SHM_STATS_FILE_VER_MAJOR 1 +#define SHM_STATS_FILE_VER_MINOR 0 + +/* header for shm stats file ("shm-stats-file") */ +struct shm_stats_file_hdr { + /* to check if the header is compatible with current haproxy version */ + struct { + uint8_t major; + uint8_t minor; + } version; +}; + +struct shm_stats_file_object { +}; + +#define SHM_STATS_FILE_MAPPING_SIZE(obj) (sizeof(struct shm_stats_file_hdr) + (obj) * sizeof(struct shm_stats_file_object)) + #endif /* _HAPROXY_STATS_FILE_T_H */ diff --git a/include/haproxy/stats-file.h b/include/haproxy/stats-file.h index d3853b4aa..102912de8 100644 --- a/include/haproxy/stats-file.h +++ b/include/haproxy/stats-file.h @@ -8,6 +8,9 @@ #include #include +extern struct shm_stats_file_hdr *shm_stats_file_hdr; +extern int shm_stats_file_max_objects; + int stats_dump_fields_file(struct buffer *out, const struct field *stats, size_t stats_count, struct show_stat_ctx *ctx); @@ -21,5 +24,6 @@ void stats_dump_file_header(int type, struct buffer *out); #define STAT_FILE_MAX_COL_COUNT (ST_I_PX_MAX*2) void apply_stats_file(void); +int shm_stats_file_prepare(void); #endif /* _HAPROXY_STATS_FILE_H */ diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c index 0100bf477..94a57e713 100644 --- a/src/cfgparse-global.c +++ b/src/cfgparse-global.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -1599,6 +1600,52 @@ static int cfg_parse_global_env_opts(char **args, int section_type, return 0; } +static int cfg_parse_global_shm_stats_file(char **args, int section_type, + struct proxy *curpx, const struct proxy *defpx, + const char *file, int line, char **err) +{ + if (!experimental_directives_allowed) { + memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]); + return -1; + } + + if (global.shm_stats_file != NULL) { + memprintf(err, "'%s' already specified.\n", args[0]); + return -1; + } + + if (!*(args[1])) { + memprintf(err, "'%s' expect one argument: a file path.\n", args[0]); + return -1; + } + + global.shm_stats_file = strdup(args[1]); + return 0; +} + +static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_type, + struct proxy *curpx, const struct proxy *defpx, + const char *file, int line, char **err) +{ + if (!experimental_directives_allowed) { + memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]); + return -1; + } + + if (shm_stats_file_max_objects != -1) { + memprintf(err, "'%s' already specified.\n", args[0]); + return -1; + } + + if (!*(args[1])) { + memprintf(err, "'%s' expect one argument: max objects number.\n", args[0]); + return -1; + } + + shm_stats_file_max_objects = atoi(args[1]); + return 0; +} + static int cfg_parse_global_parser_pause(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) @@ -1809,6 +1856,8 @@ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_GLOBAL, "quiet", cfg_parse_global_mode, KWF_DISCOVERY }, { CFG_GLOBAL, "resetenv", cfg_parse_global_env_opts, KWF_DISCOVERY }, { CFG_GLOBAL, "setenv", cfg_parse_global_env_opts, KWF_DISCOVERY }, + { CFG_GLOBAL, "shm-stats-file", cfg_parse_global_shm_stats_file }, + { CFG_GLOBAL, "shm-stats-file-max-objects", cfg_parse_global_shm_stats_file_max_objects }, { CFG_GLOBAL, "stress-level", cfg_parse_global_stress_level }, { CFG_GLOBAL, "tune.bufsize", cfg_parse_global_tune_opts }, { CFG_GLOBAL, "tune.chksize", cfg_parse_global_unsupported_opts }, diff --git a/src/haproxy.c b/src/haproxy.c index 5d06c286b..b69b90cee 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -2105,6 +2105,13 @@ static void step_init_2(int argc, char** argv) exit(1); } + /* now that config was parsed and checked + * prepare and preload shm-stats-file (if set) + */ + err_code |= shm_stats_file_prepare(); + if (err_code & (ERR_ABORT|ERR_FATAL)) + exit(1); + /* update the ready date to also account for the check time */ clock_update_date(0, 1); clock_adjust_now_offset(); diff --git a/src/stats-file.c b/src/stats-file.c index 584088bea..edd43f4e7 100644 --- a/src/stats-file.c +++ b/src/stats-file.c @@ -1,13 +1,18 @@ #include +#include +#include #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -21,7 +26,13 @@ #include #include #include +#include #include +#include + +struct shm_stats_file_hdr *shm_stats_file_hdr = NULL; +static int shm_stats_file_fd = -1; +int shm_stats_file_max_objects = -1; /* Dump all fields from into for stats-file. */ int stats_dump_fields_file(struct buffer *out, @@ -441,3 +452,109 @@ void apply_stats_file(void) ha_free(&line); fclose(file); } + +/* returns 1 if shm version is compatible with current version + * defined in stats-file-t.h or 0 if it is not compatible. + */ +static int shm_stats_file_check_ver(struct shm_stats_file_hdr *hdr) +{ + /* for now we don't even support minor version difference but this may + * change later + */ + if (hdr->version.major != SHM_STATS_FILE_VER_MAJOR || + hdr->version.minor != SHM_STATS_FILE_VER_MINOR) + return 0; + return 1; +} + +/* since shm file was opened using O_APPEND flag, let's grow + * the file by in an atomic manner (O_APPEND offers such guarantee), + * so that even if multiple processes try to grow the file simultaneously, + * the file can only grow bigger and never shrink + * + * We do this way because ftruncate() between multiple processes + * could result in the file being shrunk if one of the process + * is not aware that the file was already expanded in the meantime + * + * Returns 1 on success and 0 on failure + */ +static int shm_file_grow(unsigned int bytes) +{ + char buf[1024] = {0}; + ssize_t ret; + + while (bytes) { + ret = write(shm_stats_file_fd, buf, MIN(sizeof(buf), bytes)); + if (ret <= 0) + return 0; + bytes -= ret; + } + return 1; +} + +/* prepare and and initialize shm stats memory file as needed */ +int shm_stats_file_prepare(void) +{ + int first = 0; // process responsible for initializing the shm memory + + /* do nothing if master process or shm_stats_file not configured */ + if (master || !global.shm_stats_file) + return ERR_NONE; + + /* compute final shm_stats_file_max_objects value */ + if (shm_stats_file_max_objects == -1) + shm_stats_file_max_objects = SHM_STATS_FILE_MAX_OBJECTS * global.nbtgroups; + else + shm_stats_file_max_objects = shm_stats_file_max_objects * global.nbtgroups; + + shm_stats_file_fd = open(global.shm_stats_file, O_RDWR | O_APPEND | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (shm_stats_file_fd == -1) { + shm_stats_file_fd = open(global.shm_stats_file, O_RDWR | O_APPEND, S_IRUSR | S_IWUSR); + if (shm_stats_file_fd == -1) { + ha_alert("config: cannot open shm stats file '%s': %s\n", global.shm_stats_file, strerror(errno)); + return ERR_ALERT | ERR_FATAL; + } + } + else { + first = 1; + if (shm_file_grow(sizeof(*shm_stats_file_hdr)) == 0) { + ha_alert("config: unable to resize shm stats file '%s'\n", global.shm_stats_file); + return ERR_ALERT | ERR_FATAL; + } + } + /* mmap maximum contiguous address space for expected objects even if the backing shm is + * smaller: it will allow for on the fly shm resizing without having to remap + */ + shm_stats_file_hdr = mmap(NULL, + SHM_STATS_FILE_MAPPING_SIZE(shm_stats_file_max_objects), + PROT_READ | PROT_WRITE, MAP_SHARED, shm_stats_file_fd, 0); + if (shm_stats_file_hdr == MAP_FAILED || shm_stats_file_hdr == NULL) { + ha_alert("config: failed to map shm stats file '%s'\n", global.shm_stats_file); + return ERR_ALERT | ERR_FATAL; + } + + if (first) { + /* let's init some members */ + memset(shm_stats_file_hdr, 0, sizeof(*shm_stats_file_hdr)); + shm_stats_file_hdr->version.major = SHM_STATS_FILE_VER_MAJOR; + shm_stats_file_hdr->version.minor = SHM_STATS_FILE_VER_MINOR; + } + else if (!shm_stats_file_check_ver(shm_stats_file_hdr)) + goto err_version; + + end: + return ERR_NONE; + + err_version: + ha_warning("config: incompatible map shm stats file version '%s'\n", global.shm_stats_file); + return ERR_WARN; +} + +static void cleanup_shm_stats_file(void) +{ + if (shm_stats_file_hdr) { + munmap(shm_stats_file_hdr, SHM_STATS_FILE_MAPPING_SIZE(shm_stats_file_max_objects)); + close(shm_stats_file_fd); + } +} +REGISTER_POST_DEINIT(cleanup_shm_stats_file);