Skip to content

Commit

Permalink
proxy: ustats speedup - linear namebuf
Browse files Browse the repository at this point in the history
When dumping many hundreds or thousands of user stats we have to fetch
the name from a potentially random place in memory, which if uncached
can cause the stats command to be very intensive.

Instead we compact all of the stat names into a linear buffer and walk
them front to back when dumping, keeping memory access linear.
  • Loading branch information
dormando committed Jun 20, 2024
1 parent 024b6f3 commit 5300153
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 10 deletions.
11 changes: 7 additions & 4 deletions proto_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,13 @@ void process_proxy_stats(void *arg, ADD_STAT add_stats, void *c) {
}

// return all of the user generated stats
for (int x = 0; x < stats_num; x++) {
if (us[x].name && us[x].name[0]) {
snprintf(key_str, STAT_KEY_LEN-1, "user_%s", us[x].name);
APPEND_STAT(key_str, "%llu", (unsigned long long)counters[x]);
if (ctx->user_stats_namebuf) {
for (int x = 0; x < stats_num; x++) {
if (us[x].cname) {
char *name = ctx->user_stats_namebuf + us[x].cname;
snprintf(key_str, STAT_KEY_LEN-1, "user_%s", name);
APPEND_STAT(key_str, "%llu", (unsigned long long)counters[x]);
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ struct proxy_user_stats {
// can have significant impact.
struct proxy_user_stats_entry {
char *name;
unsigned int cname; // offset into compact name buffer
bool reset; // counter must reset this cycle
};

Expand Down Expand Up @@ -241,6 +242,7 @@ typedef struct {
#endif
int user_stats_num; // highest seen stat index
struct proxy_user_stats_entry *user_stats;
char *user_stats_namebuf; // compact linear buffer for stat names
struct proxy_tunables tunables; // NOTE: updates covered by stats_lock
struct proxy_global_stats global_stats;
// less frequently used entries down here.
Expand Down
70 changes: 70 additions & 0 deletions proxy_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,74 @@ static void *_proxy_manager_thread(void *arg) {
return NULL;
}

// TODO: only run routine if something changed.
// This compacts all of the names for proxy user stats into a linear buffer,
// which can save considerable CPU when emitting a large number of stats. It
// also saves some total memory by having one linear buffer instead of many
// potentially small aligned allocations.
static void proxy_config_stats_prep(proxy_ctx_t *ctx) {
char *oldnamebuf = ctx->user_stats_namebuf;
struct proxy_user_stats_entry *entries = ctx->user_stats;
size_t namelen = 0;

STAT_L(ctx);
// find size of new compact name buffer
for (int x = 0; x < ctx->user_stats_num; x++) {
if (entries[x].name) {
namelen += strlen(entries[x].name) + 1; // null byte
} else if (entries[x].cname) {
char *name = oldnamebuf + entries[x].cname;
namelen += strlen(name) + 1;
}
}
// start one byte into the cname buffer so we can do faster checks on if a
// name exists or not. so extend the buffer by one byte.
namelen++;

char *namebuf = calloc(1, namelen);
// copy names into the compact buffer
char *p = namebuf + 1;
for (int x = 0; x < ctx->user_stats_num; x++) {
struct proxy_user_stats_entry *e = &entries[x];
char *newname = NULL;
if (e->name) {
// skip blank names.
if (e->name[0]) {
newname = e->name;
}
} else if (e->cname) {
// else re-copy from old buffer
newname = oldnamebuf + e->cname;
}

if (newname) {
// set the buffer offset for this name
e->cname = p - namebuf;
// copy in the name
size_t nlen = strlen(newname);
memcpy(p, newname, nlen);
p += nlen;
*p = '\0'; // add null byte
p++;
} else {
// name is blank or doesn't exist, ensure we skip it.
e->cname = 0;
}

if (e->name) {
// now get rid of the name buffer.
free(e->name);
e->name = NULL;
}
}

ctx->user_stats_namebuf = namebuf;
if (oldnamebuf) {
free(oldnamebuf);
}
STAT_UL(ctx);
}

static void proxy_config_reload(proxy_ctx_t *ctx) {
LOGGER_LOG(NULL, LOG_PROXYEVENTS, LOGGER_PROXY_CONFIG, NULL, "start");
STAT_INCR(ctx, config_reloads, 1);
Expand All @@ -159,6 +227,8 @@ static void proxy_config_reload(proxy_ctx_t *ctx) {
return;
}

proxy_config_stats_prep(ctx);

// TODO (v2): create a temporary VM to test-load the worker code into.
// failing to load partway through the worker VM reloads can be
// critically bad if we're not careful about references.
Expand Down
19 changes: 13 additions & 6 deletions proxy_ustats.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,23 @@ int mcplib_add_stat(lua_State *L) {

idx--; // real slot start as 0.
if (entries[idx].name != NULL) {
// if name changed, we have to reset the counter in the slot.
// If name changed, we have to reset the counter in the slot.
// Also only free/strdup the string if it's changed.
if (strcmp(name, entries[idx].name) != 0) {
entries[idx].reset = true;
free(entries[idx].name);
entries[idx].name = strdup(name);
}
// if slot has string in it, free first
free(entries[idx].name);
// else the stat name didn't change, so don't do anything.
} else if (entries[idx].cname) {
char *oldname = ctx->user_stats_namebuf + entries[idx].cname;
if (strcmp(name, oldname) != 0) {
entries[idx].reset = true;
entries[idx].name = strdup(name);
}
} else {
entries[idx].name = strdup(name);
}
// strdup name into string slot
// TODO (v2): malloc failure.
entries[idx].name = strdup(name);
STAT_UL(ctx);

return 0;
Expand Down

0 comments on commit 5300153

Please sign in to comment.