From: Kris Van Hees <kris.van.hees@oracle.com>
To: Alan Maguire <alan.maguire@oracle.com>
Cc: dtrace@lists.linux.dev, dtrace-devel@oss.oracle.com
Subject: Re: [PATCH v5 04/11] libdtrace: Add aggregation key/value computation APIs
Date: Wed, 7 Oct 2026 10:51:44 +0000 [thread overview]
Message-ID: <asYkQBvaReka0y_V@kvh-deb-bpf> (raw)
In-Reply-To: <20260821161100.41710-5-alan.maguire@oracle.com>
I am lookingaand t this but there are a few issues already in terms of
safety. Since these are API functions, we cannot count on the caller
ensuring safety. E.g. dtrace_aggregate_keys() would result in an out of
bounds memory write if keys has room for 2 keys, num_keys is 3, and
dtagd_nkrecs >= 4. Perhaps the function should do its own allocation
and return the number of keys upon success instead?
I would do the same for the dtrace_aggregate_value() function.
On Fri, Aug 21, 2026 at 05:10:52PM +0100, Alan Maguire wrote:
> To provide a way for consumers to emit aggregation keys
> and compute aggregation values, provide new APIs in libdtrace:
>
> int dtrace_aggregate_value(const dtrace_aggdata_t *aggdata,
> dtrace_aggvalue_t *value);
> int dtrace_aggregate_keys(const dtrace_aggdata_t *data,
> dtrace_aggkey_t *keys, size_t num_keys);
> void dtrace_aggregate_key_free(dtrace_aggkey_t *key);
>
> These provide a way to compute aggregation values from snapshots,
> print them and free the representation used to print them.
>
> This allows consumers like python bindings to avoid having to
> understand aggregation internals.
>
> Signed-off-by: Alan Maguire <alan.maguire@oracle.com>
> ---
> libdtrace/dt_aggregate.c | 517 +++++++++++++++++++++++++++++++++++++++
> libdtrace/dtrace.h | 83 +++++++
> libdtrace/libdtrace.ver | 3 +
> 3 files changed, 603 insertions(+)
>
> diff --git a/libdtrace/dt_aggregate.c b/libdtrace/dt_aggregate.c
> index 1fc8294d..afc6a01a 100644
> --- a/libdtrace/dt_aggregate.c
> +++ b/libdtrace/dt_aggregate.c
> @@ -13,11 +13,15 @@
> #include <assert.h>
> #include <alloca.h>
> #include <limits.h>
> +#include <stdio.h>
> +#include <string.h>
>
> #include <libproc.h>
> #include <port.h>
> #include <dt_aggregate.h>
> #include <dt_bpf.h>
> +#include <math.h>
> +#include <limits.h>
>
> typedef struct dt_ahashent {
> struct dt_ahashent *dtahe_prev; /* prev on hash chain */
> @@ -44,6 +48,519 @@ struct dt_aggregate {
>
> #define DTRACE_AHASHSIZE 32779 /* big 'ol prime */
>
> +static int
> +dt_aggregate_read_signed(const char *addr, size_t size, int64_t *value)
> +{
> + int8_t v8;
> + int16_t v16;
> + int32_t v32;
> + int64_t v64;
> +
> + switch (size) {
> + case sizeof(v8):
> + memcpy(&v8, addr, sizeof(v8));
> + *value = v8;
> + return 0;
> + case sizeof(v16):
> + memcpy(&v16, addr, sizeof(v16));
> + *value = v16;
> + return 0;
> + case sizeof(v32):
> + memcpy(&v32, addr, sizeof(v32));
> + *value = v32;
> + return 0;
> + case sizeof(v64):
> + memcpy(&v64, addr, sizeof(v64));
> + *value = v64;
> + return 0;
> + default:
> + return -1;
> + }
> +}
> +
> +static int
> +dt_aggregate_read_unsigned(const char *addr, size_t size, uint64_t *value)
> +{
> + uint8_t v8;
> + uint16_t v16;
> + uint32_t v32;
> + uint64_t v64;
> +
> + switch (size) {
> + case sizeof(v8):
> + memcpy(&v8, addr, sizeof(v8));
> + *value = v8;
> + return 0;
> + case sizeof(v16):
> + memcpy(&v16, addr, sizeof(v16));
> + *value = v16;
> + return 0;
> + case sizeof(v32):
> + memcpy(&v32, addr, sizeof(v32));
> + *value = v32;
> + return 0;
> + case sizeof(v64):
> + memcpy(&v64, addr, sizeof(v64));
> + *value = v64;
> + return 0;
> + default:
> + return -1;
> + }
> +}
> +
> +int
> +dtrace_aggregate_value(const dtrace_aggdata_t *aggdata, dtrace_aggvalue_t *value)
> +{
> + const dtrace_aggdesc_t *agg;
> + const dtrace_recdesc_t *rec;
> + const char *addr;
> + uint64_t normal;
> +
> + if (aggdata == NULL || aggdata->dtada_desc == NULL ||
> + aggdata->dtada_data == NULL || value == NULL)
> + return aggdata && aggdata->dtada_hdl ?
> + dt_set_errno(aggdata->dtada_hdl, EINVAL) : -1;
> +
> + agg = aggdata->dtada_desc;
> + rec = &agg->dtagd_drecs[DT_AGGDATA_RECORD];
> + addr = aggdata->dtada_data + rec->dtrd_offset;
> + normal = agg->dtagd_normal ? agg->dtagd_normal : 1;
> + value->dtagv_action = rec->dtrd_action;
> + value->dtagv_normal = normal;
> + value->dtagv_bucket_count = 0;
> +
> + switch (rec->dtrd_action) {
> + case DT_AGG_AVG: {
> + const int64_t *data = (const int64_t *)addr;
> +
> + if (rec->dtrd_size < sizeof(*data) * 2)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + value->dtagv_kind = DTRACE_AGGVALUE_FLOAT;
> + value->dtagv_value.real = data[0] == 0 ? 0.0 :
> + (double)((long double)data[1] / normal / data[0]);
> + return 0;
> + }
> + case DT_AGG_STDDEV: {
> + uint64_t data[4];
> +
> + if (rec->dtrd_size < sizeof(data))
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + memcpy(data, addr, sizeof(data));
> + value->dtagv_kind = DTRACE_AGGVALUE_UNSIGNED;
> + value->dtagv_value.unsigned_integer =
> + data[0] == 0 ? 0 : dt_stddev(data, normal);
> + return 0;
> + }
> + case DT_AGG_SUM:
> + case DT_AGG_MIN:
> + case DT_AGG_MAX:
> + value->dtagv_kind = DTRACE_AGGVALUE_INTEGER;
> + if (dt_aggregate_read_signed(addr, rec->dtrd_size,
> + &value->dtagv_value.integer) != 0)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + value->dtagv_value.integer /= (int64_t)normal;
> + return 0;
> + case DT_AGG_COUNT:
> + value->dtagv_kind = DTRACE_AGGVALUE_UNSIGNED;
> + if (dt_aggregate_read_unsigned(addr, rec->dtrd_size,
> + &value->dtagv_value.unsigned_integer) != 0)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + value->dtagv_value.unsigned_integer /= normal;
> + return 0;
> + case DT_AGG_QUANTIZE: {
> + const int64_t *data = (const int64_t *)addr;
> + size_t i, nbins = rec->dtrd_size / sizeof(*data);
> +
> + if (rec->dtrd_size % sizeof(*data) != 0 ||
> + value->dtagv_buckets == NULL ||
> + value->dtagv_bucket_capacity < nbins)
> + return dt_set_errno(aggdata->dtada_hdl, EINVAL);
> + for (i = 0; i < nbins; i++) {
> + if (data[i] < 0)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + }
> + for (i = 0; i < nbins; i++) {
> + if (data[i] == 0)
> + continue;
> + value->dtagv_buckets[value->dtagv_bucket_count].bucket =
> + DTRACE_QUANTIZE_BUCKETVAL((uint64_t)i);
> + value->dtagv_buckets[value->dtagv_bucket_count].count =
> + (uint64_t)(data[i] / (int64_t)normal);
> + if (value->dtagv_buckets[value->dtagv_bucket_count].count != 0)
> + value->dtagv_bucket_count++;
> + }
> + value->dtagv_kind = DTRACE_AGGVALUE_QUANTIZED;
> + return 0;
> + }
> + case DT_AGG_LQUANTIZE: {
> + uint16_t levels = DTRACE_LQUANTIZE_LEVELS(agg->dtagd_sig);
> + uint16_t step = DTRACE_LQUANTIZE_STEP(agg->dtagd_sig);
> + int32_t base = DTRACE_LQUANTIZE_BASE(agg->dtagd_sig);
> + size_t i, nbins = (size_t)levels + 2;
> + const int64_t *data = (const int64_t *)addr;
> +
> + if (rec->dtrd_size != nbins * sizeof(*data) ||
> + value->dtagv_buckets == NULL ||
> + value->dtagv_bucket_capacity < nbins)
> + return dt_set_errno(aggdata->dtada_hdl, EINVAL);
> + for (i = 0; i < nbins; i++) {
> + if (data[i] < 0)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + }
> + for (i = 0; i < nbins; i++) {
> + int64_t count = data[i] / (int64_t)normal;
> + int64_t bucket;
> +
> + if (count == 0)
> + continue;
> + if (i == 0)
> + bucket = base - 1;
> + else if (i == levels + 1)
> + bucket = base + (int64_t)levels * step;
> + else
> + bucket = base + (int64_t)(i - 1) * step;
> + value->dtagv_buckets[value->dtagv_bucket_count++] =
> + (dtrace_aggvalue_bucket_t){ bucket, (uint64_t)count };
> + }
> + value->dtagv_kind = DTRACE_AGGVALUE_QUANTIZED;
> + return 0;
> + }
> + case DT_AGG_LLQUANTIZE: {
> + int factor = DTRACE_LLQUANTIZE_FACTOR(agg->dtagd_sig);
> + int lmag = DTRACE_LLQUANTIZE_LMAG(agg->dtagd_sig);
> + int hmag = DTRACE_LLQUANTIZE_HMAG(agg->dtagd_sig);
> + int steps = DTRACE_LLQUANTIZE_STEPS(agg->dtagd_sig);
> + int steps_factor, bin0;
> + size_t i, nbins;
> + const int64_t *data = (const int64_t *)addr;
> + long double limit = (long double)INT64_MAX;
> +
> + if (factor < 2 || steps < factor || lmag > hmag)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + steps_factor = steps / factor;
> + bin0 = 1 + (hmag - lmag + 1) * (steps - steps_factor);
> + nbins = (size_t)(hmag - lmag + 1) *
> + (steps - steps_factor) * 2 + 3;
> + if (!isfinite((double)powl(factor, hmag + 1)) ||
> + powl(factor, hmag + 1) > limit)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> +
> + if (rec->dtrd_size != nbins * sizeof(*data) ||
> + value->dtagv_buckets == NULL ||
> + value->dtagv_bucket_capacity < nbins)
> + return dt_set_errno(aggdata->dtada_hdl, EINVAL);
> + for (i = 0; i < nbins; i++) {
> + if (data[i] < 0)
> + return dt_set_errno(aggdata->dtada_hdl, EDT_BADAGG);
> + }
> + for (i = 0; i < nbins; i++) {
> + int64_t count = data[i] / (int64_t)normal;
> + int64_t bucket;
> + int left, negative, remaining, magnitude;
> +
> + if (count == 0)
> + continue;
> + if (i == 0)
> + bucket = -(int64_t)powl(factor, hmag + 1);
> + else if (i == nbins - 1)
> + bucket = (int64_t)powl(factor, hmag + 1);
> + else if (i == (size_t)bin0)
> + bucket = 0;
> + else {
> + left = (int)i - bin0;
> + negative = left < 0;
> + remaining = negative ? bin0 - (int)i : left;
> + magnitude = lmag;
> + if (lmag == 0 && steps > factor) {
> + for (int n = 2; n <= factor && remaining > 0; n++) {
> + remaining -= steps_factor;
> + if (remaining == 0) {
> + bucket = negative ? -n : n;
> + goto ll_have_bucket;
> + }
> + }
> + magnitude++;
> + }
> + bucket = negative ? -1 : 1;
> + while (magnitude <= hmag) {
> + int64_t scale = (int64_t)(powl(factor, magnitude + 1) /
> + (double)steps);
> + int n;
> +
> + for (n = steps_factor + 1; n <= steps; n++) {
> + if (--remaining == 0) {
> + bucket = n * scale * (negative ? -1 : 1);
> + goto ll_have_bucket;
> + }
> + }
> + magnitude++;
> + }
> + bucket *= (int64_t)powl(factor, hmag + 1);
> + }
> +ll_have_bucket:
> + value->dtagv_buckets[value->dtagv_bucket_count++] =
> + (dtrace_aggvalue_bucket_t){ bucket, (uint64_t)count };
> + }
> + value->dtagv_kind = DTRACE_AGGVALUE_QUANTIZED;
> + return 0;
> + }
> + default:
> + value->dtagv_kind = DTRACE_AGGVALUE_RAW;
> + value->dtagv_value.raw.data = addr;
> + value->dtagv_value.raw.size = rec->dtrd_size;
> + return 0;
> + }
> +}
> +
> +void dtrace_aggregate_key_free(dtrace_aggkey_t *key)
> +{
> + size_t i;
> +
> + if (!key)
> + return;
> + switch (key->dtagk_kind) {
> + case DTRACE_AGGKEY_BYTES:
> + free(key->dtagk_value.bytes.data);
> + break;
> + case DTRACE_AGGKEY_STRING:
> + case DTRACE_AGGKEY_SYMBOL:
> + case DTRACE_AGGKEY_MODULE:
> + free(key->dtagk_value.name);
> + break;
> + case DTRACE_AGGKEY_ADDRESS:
> + break;
> + case DTRACE_AGGKEY_STACK:
> + for (i = 0; i < key->dtagk_value.stack.depth; i++)
> + free(key->dtagk_value.stack.frames[i]);
> + free(key->dtagk_value.stack.frames);
> + break;
> + default:
> + break;
> + }
> + memset(key, 0, sizeof(*key));
> +}
> +
> +int dtrace_aggregate_keys(const dtrace_aggdata_t *data,
> + dtrace_aggkey_t *keys, size_t num_keys)
> +{
> + int error = EDT_NOMEM;
> + dtrace_hdl_t *dtp;
> + size_t i, n;
> +
> + if (!data || !data->dtada_desc)
> + return data && data->dtada_hdl ?
> + dt_set_errno(data->dtada_hdl, EINVAL) : -1;
> + dtp = data->dtada_hdl;
> + if (data->dtada_desc->dtagd_nkrecs == 0)
> + return 0;
> + n = data->dtada_desc->dtagd_nkrecs - 1;
> + if (n == 0)
> + return 0;
> + if (!data->dtada_key)
> + return dt_set_errno(dtp, EDT_BADAGG);
> + if (!keys || num_keys < n)
> + return dt_set_errno(dtp, EINVAL);
> + memset(keys, 0, n * sizeof(*keys));
> +
> + for (i = 0; i < n; i++) {
> + const dtrace_recdesc_t *rec = &data->dtada_desc->dtagd_krecs[i + 1];
> + char *addr = data->dtada_key + rec->dtrd_offset;
> + if ((size_t)rec->dtrd_offset > data->dtada_desc->dtagd_ksize ||
> + rec->dtrd_size > data->dtada_desc->dtagd_ksize -
> + (size_t)rec->dtrd_offset)
> + goto badagg;
> +
> + switch (rec->dtrd_action) {
> + case DTRACEACT_STACK:
> + case DTRACEACT_USTACK:
> + case DTRACEACT_JSTACK: {
> + uint32_t depth, max, frames, j, tgid = 0;
> +
> + if (rec->dtrd_size < 2 * sizeof(uint64_t) ||
> + (rec->dtrd_action != DTRACEACT_STACK &&
> + rec->dtrd_size < 4 * sizeof(uint64_t)))
> + goto badagg;
> + memcpy(&depth, addr, sizeof(depth));
> + max = rec->dtrd_size >= 2 * sizeof(uint64_t) ?
> + rec->dtrd_size / sizeof(uint64_t) - 2 : 0;
> + frames = depth < max ? depth : max;
> + if (rec->dtrd_action != DTRACEACT_STACK) {
> + memcpy(&tgid, addr + 3 * sizeof(uint32_t),
> + sizeof(tgid));
> + }
> +
> + keys[i].dtagk_kind = DTRACE_AGGKEY_STACK;
> + keys[i].dtagk_value.stack.frames = frames == 0 ? NULL :
> + calloc(frames, sizeof(char *));
> + keys[i].dtagk_value.stack.depth = 0;
> + if (frames != 0 && !keys[i].dtagk_value.stack.frames)
> + goto error;
> + for (j = 0; j < frames; j++) {
> + char text[PATH_MAX * 2];
> + uint64_t pc;
> + int rc;
> +
> + memcpy(&pc, addr + (j + 2) * sizeof(uint64_t),
> + sizeof(pc));
> + if (pc == 0)
> + break;
> + rc = rec->dtrd_action == DTRACEACT_STACK ?
> + dtrace_addr2str(dtp, pc, text,
> + sizeof(text)) :
> + dtrace_uaddr2str(dtp, tgid, pc,
> + text, sizeof(text));
> + if (rc < 0) {
> + error = EDT_BADAGG;
> + goto error;
> + }
> + if (!(keys[i].dtagk_value.stack.frames[j] = strdup(text)))
> + goto error;
> + keys[i].dtagk_value.stack.depth++;
> + }
> + break;
> + }
> + case DTRACEACT_SYM:
> + case DTRACEACT_MOD: {
> + dtrace_syminfo_t info;
> + char text[PATH_MAX * 2];
> + uint64_t pc;
> +
> + memcpy(&pc, addr, sizeof(pc));
> +
> + if (dtrace_lookup_by_addr(dtp, pc,
> + NULL, &info) == 0) {
> + snprintf(text, sizeof(text), "%s%s%s",
> + info.object,
> + rec->dtrd_action == DTRACEACT_SYM ? "`" : "",
> + rec->dtrd_action == DTRACEACT_SYM ? info.name : "");
> + } else {
> + snprintf(text, sizeof(text), "0x%llx",
> + (unsigned long long)pc);
> + }
> + keys[i].dtagk_kind = rec->dtrd_action == DTRACEACT_SYM ?
> + DTRACE_AGGKEY_SYMBOL : DTRACE_AGGKEY_MODULE;
> + keys[i].dtagk_value.name = strdup(text);
> + if (!keys[i].dtagk_value.name)
> + goto error;
> + break;
> + }
> + case DTRACEACT_UADDR:
> + case DTRACEACT_USYM:
> + case DTRACEACT_UMOD: {
> + char text[PATH_MAX * 2];
> + uint32_t tgid;
> + uint64_t pc;
> +
> + /*
> + * The target PID occupies the first word and the address
> + * the second; the record is aligned to 64 bits.
> + */
> + if (rec->dtrd_size < 2 * sizeof(uint64_t))
> + goto badagg;
> + memcpy(&tgid, addr, sizeof(tgid));
> + memcpy(&pc, addr + sizeof(uint64_t), sizeof(pc));
> + if (rec->dtrd_action == DTRACEACT_UADDR) {
> + keys[i].dtagk_kind = DTRACE_AGGKEY_ADDRESS;
> + keys[i].dtagk_value.user_address.pid = tgid;
> + keys[i].dtagk_value.user_address.address = pc;
> + break;
> + }
> +
> + if (dtrace_uaddr2str(dtp, tgid, pc, text,
> + sizeof(text)) < 0)
> + goto badagg;
> + if (rec->dtrd_action == DTRACEACT_UMOD) {
> + char *tick = strchr(text, '`');
> + if (tick)
> + *tick = '\0';
> + }
> + keys[i].dtagk_kind = rec->dtrd_action == DTRACEACT_USYM ?
> + DTRACE_AGGKEY_SYMBOL : DTRACE_AGGKEY_MODULE;
> + keys[i].dtagk_value.name = strdup(text);
> + if (!keys[i].dtagk_value.name)
> + goto error;
> + break;
> + }
> + default:
> + keys[i].dtagk_kind = DTRACE_AGGKEY_INTEGER;
> + switch (rec->dtrd_size) {
> + case 1: {
> + int8_t v;
> +
> + memcpy(&v, addr, 1);
> + keys[i].dtagk_value.integer = v;
> + break;
> + }
> + case 2: {
> + int16_t v;
> +
> + memcpy(&v, addr, 2);
> + keys[i].dtagk_value.integer = v;
> + break;
> + }
> + case 4: {
> + int32_t v;
> +
> + memcpy(&v, addr, 4);
> + keys[i].dtagk_value.integer = v;
> + break;
> + }
> + case 8: {
> + int64_t v;
> +
> + memcpy(&v, addr, 8);
> + keys[i].dtagk_value.integer = v;
> + break;
> + }
> + default: {
> + char *nul = memchr(addr, '\0', rec->dtrd_size);
> +
> + /*
> + * This is a heuristic: key descriptors do not
> + * retain type information. DTrace strings are
> + * NUL-terminated and padded with zeros to their
> + * fixed record size.
> + */
> + if (nul != NULL) {
> + char *p;
> +
> + for (p = nul + 1; p < addr + rec->dtrd_size; p++) {
> + if (*p != '\0')
> + break;
> + }
> + if (p == addr + rec->dtrd_size) {
> + keys[i].dtagk_kind = DTRACE_AGGKEY_STRING;
> + keys[i].dtagk_value.name = strndup(addr,
> + rec->dtrd_size);
> + if (!keys[i].dtagk_value.name)
> + goto error;
> + break;
> + }
> + }
> + keys[i].dtagk_kind = DTRACE_AGGKEY_BYTES;
> + keys[i].dtagk_value.bytes.data = malloc(rec->dtrd_size);
> + if (!keys[i].dtagk_value.bytes.data)
> + goto error;
> + memcpy(keys[i].dtagk_value.bytes.data, addr,
> + rec->dtrd_size);
> + keys[i].dtagk_value.bytes.size = rec->dtrd_size;
> + break;
> + }
> + }
> + break;
> + }
> + }
> + return (int)n;
> +error:
> + for (i = 0; i < n; i++)
> + dtrace_aggregate_key_free(&keys[i]);
> + return dt_set_errno(dtp, error);
> +badagg:
> + for (i = 0; i < n; i++)
> + dtrace_aggregate_key_free(&keys[i]);
> + return dt_set_errno(dtp, EDT_BADAGG);
> +}
> +
> /*
> * Because qsort(3C) does not allow an argument to be passed to a comparison
> * function, the variables that affect comparison must regrettably be global;
> diff --git a/libdtrace/dtrace.h b/libdtrace/dtrace.h
> index 82965fbd..a108080f 100644
> --- a/libdtrace/dtrace.h
> +++ b/libdtrace/dtrace.h
> @@ -379,6 +379,89 @@ struct dtrace_aggdata {
> caddr_t *dtada_percpu; /* per CPU data, if avail */
> };
>
> +typedef enum dtrace_aggvalue_kind {
> + DTRACE_AGGVALUE_INTEGER,
> + DTRACE_AGGVALUE_UNSIGNED,
> + DTRACE_AGGVALUE_FLOAT,
> + DTRACE_AGGVALUE_RAW,
> + DTRACE_AGGVALUE_QUANTIZED
> +} dtrace_aggvalue_kind_t;
> +
> +typedef struct dtrace_aggvalue_bucket {
> + int64_t bucket;
> + uint64_t count;
> +} dtrace_aggvalue_bucket_t;
> +
> +/* Quantized buckets are supplied by the caller through dtagv_buckets. */
> +typedef struct dtrace_aggvalue {
> + dtrace_aggvalue_kind_t dtagv_kind;
> + uint16_t dtagv_action;
> + uint64_t dtagv_normal;
> + dtrace_aggvalue_bucket_t *dtagv_buckets;
> + size_t dtagv_bucket_capacity;
> + size_t dtagv_bucket_count;
> + union {
> + int64_t integer;
> + uint64_t unsigned_integer;
> + double real;
> + struct {
> + const void *data;
> + size_t size;
> + } raw;
> + } dtagv_value;
> +} dtrace_aggvalue_t;
> +
> +typedef enum dtrace_aggkey_kind {
> + DTRACE_AGGKEY_INTEGER,
> + DTRACE_AGGKEY_STRING,
> + DTRACE_AGGKEY_BYTES,
> + DTRACE_AGGKEY_STACK,
> + DTRACE_AGGKEY_SYMBOL,
> + DTRACE_AGGKEY_MODULE,
> + DTRACE_AGGKEY_USER_ADDRESS,
> + DTRACE_AGGKEY_ADDRESS = DTRACE_AGGKEY_USER_ADDRESS
> +} dtrace_aggkey_kind_t;
> +
> +typedef struct dtrace_aggkey {
> + dtrace_aggkey_kind_t dtagk_kind;
> + union {
> + int64_t integer;
> + struct {
> + char *data;
> + size_t size;
> + } bytes;
> + struct {
> + char **frames;
> + size_t depth;
> + } stack;
> + char *name;
> + /* User addresses include the target PID to disambiguate ASLR. */
> + struct {
> + uint32_t pid;
> + uint64_t address;
> + } user_address;
> + } dtagk_value;
> +} dtrace_aggkey_t;
> +
> +/*
> + * Decode all keys in an aggregation snapshot. The caller supplies an array
> + * with capacity for dtagd_nkrecs - 1 descriptors. Returned strings, byte
> + * buffers, and stack frames are owned by the caller and must be released
> + * with dtrace_aggregate_key_free(). Zero-key aggregations return zero.
> + */
> +extern int dtrace_aggregate_keys(const dtrace_aggdata_t *, dtrace_aggkey_t *,
> + size_t);
> +extern void dtrace_aggregate_key_free(dtrace_aggkey_t *);
> +
> +/*
> + * Decode one aggregation value. Integer values can be signed or unsigned.
> + * Raw data points into the snapshot and is valid only while that snapshot
> + * remains valid. Quantized values require a caller-provided bucket array and
> + * capacity.
> + */
> +extern int dtrace_aggregate_value(const dtrace_aggdata_t *,
> + dtrace_aggvalue_t *);
> +
> typedef struct dtrace_print_aggdata {
> dtrace_hdl_t *dtpa_dtp; /* handle to DTrace library */
> dtrace_aggid_t dtpa_id; /* aggregation variable */
> diff --git a/libdtrace/libdtrace.ver b/libdtrace/libdtrace.ver
> index edbd5a1d..87d561b2 100644
> --- a/libdtrace/libdtrace.ver
> +++ b/libdtrace/libdtrace.ver
> @@ -8,8 +8,11 @@ LIBDTRACE_1.0 {
> global:
> dtrace_addr2str;
> dtrace_aggregate_clear;
> + dtrace_aggregate_keys;
> + dtrace_aggregate_key_free;
> dtrace_aggregate_print;
> dtrace_aggregate_snap;
> + dtrace_aggregate_value;
> dtrace_aggregate_walk;
> dtrace_aggregate_walk_joined;
> dtrace_aggregate_walk_keyrevsorted;
> --
> 2.43.5
>
next prev parent reply other threads:[~2026-10-07 10:53 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 16:10 [PATCH v5 00/11] Add python, PCP support via libdtrace interfaces Alan Maguire
2026-08-21 16:10 ` [PATCH v5 01/11] libdtrace: Support multiple DTrace handles per process Alan Maguire
2026-10-07 9:26 ` Kris Van Hees
2026-08-21 16:10 ` [PATCH v5 02/11] libdtrace: share vmlinux BTF/CTF globally to support faster startup Alan Maguire
2026-10-07 10:05 ` Kris Van Hees
2026-08-21 16:10 ` [PATCH v5 03/11] libdtrace: avoid BTF probes for ranged built-in modules Alan Maguire
2026-10-07 10:20 ` Kris Van Hees
2026-08-21 16:10 ` [PATCH v5 04/11] libdtrace: Add aggregation key/value computation APIs Alan Maguire
2026-10-07 10:51 ` Kris Van Hees [this message]
2026-08-21 16:10 ` [PATCH v5 05/11] python: Add cpython bindings for libdtrace Alan Maguire
2026-10-05 16:23 ` Kris Van Hees
2026-08-21 16:10 ` [PATCH v5 06/11] dtrace.spec: add python bindings packaging Alan Maguire
2026-08-21 16:10 ` [PATCH v5 07/11] runtest.sh: Prepare Python bindings for in-tree testing Alan Maguire
2026-08-21 16:10 ` [PATCH v5 08/11] test: add tests for python bindings Alan Maguire
2026-08-21 16:10 ` [PATCH v5 09/11] dtrace: Add a PCP PMDA to expose DTrace data as metrics Alan Maguire
2026-08-21 16:10 ` [PATCH v5 10/11] dtrace.spec: Add PCP PMDA packaging pcp-pmda-dtrace Alan Maguire
2026-08-21 16:10 ` [PATCH v5 11/11] test: Add a test for the PCP ManagedDTraceScript interface Alan Maguire
2026-10-05 16:28 ` Kris Van Hees
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=asYkQBvaReka0y_V@kvh-deb-bpf \
--to=kris.van.hees@oracle.com \
--cc=alan.maguire@oracle.com \
--cc=dtrace-devel@oss.oracle.com \
--cc=dtrace@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.