testsAndMisc/linux_configuration/C/atop_agg/atop_agg_emit.c
Krzysztof kuhy Rudnicki da1e0d3588 atop_agg: split atop_agg.c 478 -> 204 + state + emit
Three units along the file's own concerns:

  atop_agg.c (204)        ingest -- tokenising atop output, folding lines, main
  atop_agg_state.c (113)  the per-PID hash tables, slot lookup, epochs, alloc
  atop_agg_emit.c (178)   the ranked, name-aggregated report

Table geometry and the four table functions that now cross a unit boundary go
in a private atop_agg_internal.h. Those four lose `static` -- the one
visibility change here.

The Makefile edit is the load-bearing part: SRC had to grow or the build would
have linked the old single unit and the suite would have passed with the new
files unused. Checked by reading the compile line, not the exit status.

I had written this file off as unverifiable earlier. Wrong: the Makefile has
had test and coverage targets all along. Coverage is 92.9% lines / 100%
functions on the same 211 source lines, before and after -- the best verifier
of anything left over the cap. Production binary builds at -O2 and runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 10:49:46 +02:00

179 lines
4.6 KiB
C

/*
* Reporting half of atop_agg: turning the accumulated per-PID tables into the
* ranked, name-aggregated summary the report consumes.
*
* Split from atop_agg.c, which owns ingest (tokenising atop output and folding
* each line into the tables). The two share only the table geometry in
* atop_agg_internal.h and the State type in atop_agg.h.
*/
#include "atop_agg.h"
#include "atop_agg_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int cmp_long(const void *a, const void *b)
{
long la = *(const long *)a;
long lb = *(const long *)b;
if (la < lb)
{
return -1;
}
if (la > lb)
{
return 1;
}
return 0;
}
/* FNV-1a 32-bit over a NUL-terminated string; used to key the name table. */
static unsigned int fnv1a(const char *s)
{
unsigned int h = 2166136261u;
while (*s)
{
h ^= (unsigned char)*s++;
h *= 16777619u;
}
return h;
}
/*
* Per-name aggregate, built in a second pass over cpu/ram tables so that
* the caller only has to parse a few thousand output rows instead of one
* row per PID. The name table is deliberately oversized (64k slots for an
* expected few-thousand names) to keep linear-probe chains short.
*/
#define NAME_CAP_BITS 16
#define NAME_CAP (1u << NAME_CAP_BITS)
#define NAME_MASK (NAME_CAP - 1u)
typedef struct
{
char name[ATOP_AGG_NAME_MAX];
long cpu_ticks;
int cpu_pids;
long peak_kb;
long sum_avg_kb;
int rss_samples;
int ram_pids;
char used;
} NameAgg;
static NameAgg *name_slot(NameAgg *table, const char *name)
{
unsigned int h = fnv1a(name);
for (unsigned int probes = 0; probes < NAME_CAP; probes++, h++)
{
NameAgg *slot = &table[h & NAME_MASK];
if (!slot->used)
{
slot->used = 1;
/* copy_name already enforced \0-termination on the source. */
size_t i = 0;
while (name[i] && i + 1 < sizeof(slot->name))
{
slot->name[i] = name[i];
i++;
}
slot->name[i] = '\0';
return slot;
}
if (strcmp(slot->name, name) == 0)
{
return slot;
}
}
return NULL;
}
/* Write the aggregated summary to *out* in the documented TSV schema. */
void emit_results(State *s, FILE *out)
{
long start_epoch = 0;
long end_epoch = 0;
long median_interval = 0;
if (s->n_epochs > 0)
{
qsort(s->epochs, (size_t)s->n_epochs, sizeof(long), cmp_long);
start_epoch = s->epochs[0];
end_epoch = s->epochs[s->n_epochs - 1];
if (s->n_epochs >= 2)
{
long deltas[MAX_EPOCHS];
for (int i = 0; i < s->n_epochs - 1; i++)
{
deltas[i] = s->epochs[i + 1] - s->epochs[i];
}
qsort(deltas, (size_t)(s->n_epochs - 1), sizeof(long), cmp_long);
median_interval = deltas[(s->n_epochs - 1) / 2];
}
}
fprintf(out, "W\t%ld\t%ld\t%d\t%ld\n", start_epoch, end_epoch, s->n_epochs, median_interval);
NameAgg *names = calloc(NAME_CAP, sizeof(NameAgg));
if (!names)
{
return;
}
for (unsigned int i = 0; i < HASH_CAP; i++)
{
PidCpu *slot = &s->cpu[i];
if (slot->pid == 0)
{
continue;
}
long delta = slot->last_ticks;
if (slot->samples >= 2)
{
delta = slot->last_ticks - slot->first_ticks;
if (delta < 0)
{
delta = 0;
}
}
NameAgg *na = name_slot(names, slot->name);
if (!na)
{
continue;
}
na->cpu_ticks += delta;
na->cpu_pids++;
}
for (unsigned int i = 0; i < HASH_CAP; i++)
{
PidRam *slot = &s->ram[i];
if (slot->pid == 0)
{
continue;
}
long avg_kb = slot->samples ? slot->sum_kb / slot->samples : 0;
NameAgg *na = name_slot(names, slot->name);
if (!na)
{
continue;
}
if (slot->peak_kb > na->peak_kb)
{
na->peak_kb = slot->peak_kb;
}
na->sum_avg_kb += avg_kb;
na->rss_samples++;
na->ram_pids++;
}
for (unsigned int i = 0; i < NAME_CAP; i++)
{
NameAgg *na = &names[i];
if (!na->used)
{
continue;
}
int pids = na->cpu_pids > na->ram_pids ? na->cpu_pids : na->ram_pids;
fprintf(out, "N\t%s\t%ld\t%ld\t%ld\t%d\t%d\n", na->name, na->cpu_ticks, na->peak_kb,
na->sum_avg_kb, na->rss_samples, pids);
}
free(names);
}