Data-Histogram-Shared

 view release on metacpan or  search on metacpan

Shared.xs  view on Meta::CPAN

    RETVAL

SV *
new_memfd(class, name = &PL_sv_undef, lowest = 1, highest = 3600000000LL, sig_figs = 3)
    const char *class
    SV *name
    IV lowest
    IV highest
    int sig_figs
  PREINIT:
    char errbuf[HIST_ERR_BUFLEN];
  CODE:
    const char *nm = SvOK(name) ? SvPV_nolen(name) : NULL;   /* undef -> default label */
    HistHandle *h = hist_create_memfd(nm, (int64_t)lowest, (int64_t)highest,
                                      (int32_t)sig_figs, errbuf);   /* validates args into errbuf */
    if (!h) croak("Data::Histogram::Shared->new_memfd: %s", errbuf);
    MAKE_OBJ(class, h);
  OUTPUT:
    RETVAL

SV *
new_from_fd(class, fd)
    const char *class
    int fd
  PREINIT:
    char errbuf[HIST_ERR_BUFLEN];
  CODE:
    HistHandle *h = hist_open_fd(fd, errbuf);
    if (!h) croak("Data::Histogram::Shared->new_from_fd: %s", errbuf);
    MAKE_OBJ(class, h);
  OUTPUT:
    RETVAL

void
DESTROY(self)
    SV *self
  CODE:
    if (sv_isobject(self) && sv_derived_from(self, "Data::Histogram::Shared")) {
        HistHandle *h = INT2PTR(HistHandle*, SvIV(SvRV(self)));
        if (h) { sv_setiv(SvRV(self), 0); hist_destroy(h); }   /* null first: activates EXTRACT's use-after-destroy croak + makes a double DESTROY a no-op */
    }

IV
record(self, value, count = 1)
    SV *self
    IV value
    UV count
  PREINIT:
    EXTRACT(self);
    int64_t idx;
    IV total;
  CODE:
    /* Range-check + index-compute BEFORE locking so a croak holds no lock. */
    if (value < 0)
        croak("Data::Histogram::Shared->record: negative value (%lld)", (long long)value);
    idx = hist_index_for(h, (int64_t)value);
    if (idx < 0)
        croak("Data::Histogram::Shared->record: value %lld exceeds highest_trackable_value (%lld)",
              (long long)value, (long long)h->hdr->highest);
    hist_rwlock_wrlock(h);
    hist_record_locked(h, (int64_t)value, (int64_t)count);
    total = (IV)h->hdr->total_count;
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    hist_rwlock_wrunlock(h);
    RETVAL = total;
  OUTPUT:
    RETVAL

UV
record_many(self, values)
    SV *self
    SV *values
  PREINIT:
    EXTRACT(self);
    AV *av;
    IV  top;
  CODE:
    if (!SvROK(values) || SvTYPE(SvRV(values)) != SVt_PVAV)
        croak("Data::Histogram::Shared->record_many: expected an array reference");
    av = (AV *)SvRV(values);
    top = av_len(av);                     /* last index, -1 if empty */
    {
        STRLEN cnt = (top >= 0) ? (STRLEN)(top + 1) : 0, i;
        int64_t *vals = NULL;
        if (cnt) {                                       /* resolve + range-check ALL before locking */
            Newx(vals, cnt, int64_t); SAVEFREEPV(vals);  /* freed on return OR unwind */
            for (i = 0; i < cnt; i++) {                  /* a croak here holds NO lock; SAVEFREEPV cleans up */
                SV **el = av_fetch(av, (SSize_t)i, 0);
                IV v = (el && *el) ? SvIV(*el) : 0;
                if (v < 0)
                    croak("Data::Histogram::Shared->record_many: negative value (%lld)", (long long)v);
                if (hist_index_for(h, (int64_t)v) < 0)
                    croak("Data::Histogram::Shared->record_many: value %lld exceeds highest_trackable_value (%lld)",
                          (long long)v, (long long)h->hdr->highest);
                vals[i] = (int64_t)v;
            }
        }
        hist_rwlock_wrlock(h);                            /* locked region: NO croak-capable calls */
        for (i = 0; i < cnt; i++) hist_record_locked(h, vals[i], 1);
        __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);  /* a call always counts, even an empty batch */
        hist_rwlock_wrunlock(h);
        RETVAL = (UV)cnt;
    }
  OUTPUT:
    RETVAL

IV
value_at_percentile(self, p)
    SV *self
    double p
  PREINIT:
    EXTRACT(self);
    IV v;
  CODE:
    hist_rwlock_rdlock(h);
    v = (IV)hist_value_at_percentile_locked(h, p);
    hist_rwlock_rdunlock(h);
    RETVAL = v;
  OUTPUT:
    RETVAL

IV
count_at_value(self, value)
    SV *self
    IV value
  PREINIT:
    EXTRACT(self);
    int64_t idx;
    IV c;
  CODE:
    if (value < 0)
        croak("Data::Histogram::Shared->count_at_value: negative value (%lld)", (long long)value);
    idx = hist_index_for(h, (int64_t)value);
    if (idx < 0)
        croak("Data::Histogram::Shared->count_at_value: value %lld exceeds highest_trackable_value (%lld)",
              (long long)value, (long long)h->hdr->highest);
    hist_rwlock_rdlock(h);
    c = (IV)hist_counts(h)[idx];
    hist_rwlock_rdunlock(h);
    RETVAL = c;
  OUTPUT:
    RETVAL

IV
min(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    int64_t mn, total;
  CODE:
    hist_rwlock_rdlock(h);
    total = h->hdr->total_count;
    mn    = h->hdr->min_value;
    hist_rwlock_rdunlock(h);
    RETVAL = (IV)(total == 0 ? 0 : mn);
  OUTPUT:
    RETVAL

IV
max(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    IV mx;
  CODE:
    hist_rwlock_rdlock(h);
    mx = (IV)h->hdr->max_value;
    hist_rwlock_rdunlock(h);
    RETVAL = mx;
  OUTPUT:
    RETVAL

double
mean(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    double m;
  CODE:
    hist_rwlock_rdlock(h);
    m = hist_mean_locked(h);
    hist_rwlock_rdunlock(h);
    RETVAL = m;
  OUTPUT:
    RETVAL

UV
total_count(self)
    SV *self
  PREINIT:
    EXTRACT(self);
    UV t;
  CODE:
    hist_rwlock_rdlock(h);
    t = (UV)h->hdr->total_count;
    hist_rwlock_rdunlock(h);
    RETVAL = t;
  OUTPUT:
    RETVAL

void
merge(self, other)
    SV *self
    SV *other
  PREINIT:
    EXTRACT(self);
  CODE:
    if (!sv_isobject(other) || !sv_derived_from(other, "Data::Histogram::Shared"))
        croak("Data::Histogram::Shared->merge: expected a Data::Histogram::Shared object");
    HistHandle *o = INT2PTR(HistHandle*, SvIV(SvRV(other)));
    if (!o) croak("Attempted to use a destroyed Data::Histogram::Shared object");

    /* Geometry is immutable after creation -- compare lock-free, croak BEFORE
     * allocating, so a mismatch holds no lock and leaks no buffer. */
    if (o->hdr->lowest != h->hdr->lowest ||
        o->hdr->highest != h->hdr->highest ||
        o->hdr->sig_figs != h->hdr->sig_figs ||
        o->hdr->counts_len != h->hdr->counts_len ||
        o->hdr->unit_magnitude != h->hdr->unit_magnitude ||
        o->hdr->sub_bucket_mask != h->hdr->sub_bucket_mask)
        croak("Data::Histogram::Shared->merge: geometry mismatch "
              "(lowest=%lld/highest=%lld/sig=%d vs lowest=%lld/highest=%lld/sig=%d)",
              (long long)h->hdr->lowest, (long long)h->hdr->highest, (int)h->hdr->sig_figs,
              (long long)o->hdr->lowest, (long long)o->hdr->highest, (int)o->hdr->sig_figs);

    /* Snapshot the other's counts (+ total/min/max) under its read lock into a
     * temp buffer, then release before taking self's write lock.  Copying to a
     * temp avoids holding two locks at once (deadlock-free regardless of
     * acquisition order between two processes merging each other). */
    {
        int64_t counts_len = h->hdr->counts_len;
        int64_t other_total, other_min, other_max;
        int64_t *tmp;
        Newx(tmp, (size_t)counts_len, int64_t);
        SAVEFREEPV(tmp);                 /* freed on normal return OR croak unwind */
        hist_rwlock_rdlock(o);
        memcpy(tmp, hist_counts(o), (size_t)counts_len * sizeof(int64_t));
        other_total = o->hdr->total_count;
        other_min   = o->hdr->min_value;
        other_max   = o->hdr->max_value;
        hist_rwlock_rdunlock(o);

        hist_rwlock_wrlock(h);
        if (other_total > 0) hist_merge_counts(hist_counts(h), tmp, counts_len);   /* empty other -> nothing to add */
        if (h->hdr->total_count > INT64_MAX - other_total) h->hdr->total_count = INT64_MAX;
        else h->hdr->total_count += other_total;
        if (other_total > 0) {  /* only adopt min/max if other actually recorded something */
            if (other_min < h->hdr->min_value) h->hdr->min_value = other_min;
            if (other_max > h->hdr->max_value) h->hdr->max_value = other_max;
        }
        __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
        hist_rwlock_wrunlock(h);
    }

void
reset(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    hist_rwlock_wrlock(h);
    hist_reset_locked(h);
    __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
    hist_rwlock_wrunlock(h);

IV
lowest(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = (IV)h->hdr->lowest;
  OUTPUT:
    RETVAL

IV
highest(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = (IV)h->hdr->highest;
  OUTPUT:
    RETVAL

int
sig_figs(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = (int)h->hdr->sig_figs;
  OUTPUT:
    RETVAL

IV
counts_len(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = (IV)h->hdr->counts_len;
  OUTPUT:
    RETVAL

SV *
stats(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    {
        int64_t total, mn, mx, counts_len, lowest, highest;
        int32_t sig_figs, bucket_count, sub_bucket_count;
        uint64_t ops;
        double mean;
        /* Snapshot under the lock; do all (croak-capable) Perl allocation after
           releasing it -- so an OOM in newHV/newSV* can never strand the lock. */
        hist_rwlock_rdlock(h);
        total            = h->hdr->total_count;
        mn               = h->hdr->min_value;
        mx               = h->hdr->max_value;
        counts_len       = h->hdr->counts_len;
        lowest           = h->hdr->lowest;
        highest          = h->hdr->highest;
        sig_figs         = h->hdr->sig_figs;
        bucket_count     = h->hdr->bucket_count;
        sub_bucket_count = h->hdr->sub_bucket_count;
        ops              = h->hdr->stat_ops;
        mean             = hist_mean_locked(h);
        hist_rwlock_rdunlock(h);

        HV *hv = newHV();
        hv_stores(hv, "lowest",           newSViv((IV)lowest));
        hv_stores(hv, "highest",          newSViv((IV)highest));
        hv_stores(hv, "sig_figs",         newSViv((IV)sig_figs));
        hv_stores(hv, "count",            newSViv((IV)total));
        hv_stores(hv, "min",              newSViv((IV)(total == 0 ? 0 : mn)));
        hv_stores(hv, "max",              newSViv((IV)mx));
        hv_stores(hv, "mean",             newSVnv(mean));
        hv_stores(hv, "counts_len",       newSViv((IV)counts_len));
        hv_stores(hv, "bucket_count",     newSViv((IV)bucket_count));
        hv_stores(hv, "sub_bucket_count", newSViv((IV)sub_bucket_count));
        hv_stores(hv, "ops",              newSVuv((UV)ops));
        hv_stores(hv, "mmap_size",        newSVuv((UV)h->mmap_size));
        RETVAL = newRV_noinc((SV *)hv);
    }
  OUTPUT:
    RETVAL

SV *
path(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
  OUTPUT:
    RETVAL

int
memfd(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    RETVAL = h->backing_fd;
  OUTPUT:
    RETVAL

void
sync(self)
    SV *self
  PREINIT:
    EXTRACT(self);
  CODE:
    if (hist_msync(h) != 0) croak("sync: %s", strerror(errno));

void
unlink(self, ...)
    SV *self
  CODE:
    if (sv_isobject(self) && sv_derived_from(self, "Data::Histogram::Shared")) {
        HistHandle *h = INT2PTR(HistHandle*, SvIV(SvRV(self)));
        if (h && h->path) unlink(h->path);
    } else if (items >= 2 && SvOK(ST(1))) {
        unlink(SvPV_nolen(ST(1)));
    }



( run in 0.424 second using v1.01-cache-2.11-cpan-bbe5e583499 )