mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 13:27:47 +01:00
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
179 lines
4.6 KiB
C
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);
|
|
}
|