File Coverage

Shared.xs
Criterion Covered Total %
statement 165 166 99.4
branch 144 230 62.6
condition n/a
subroutine n/a
pod n/a
total 309 396 78.0


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6             #include "hist.h"
7              
8             #define EXTRACT(sv) \
9             if (!sv_isobject(sv) || !sv_derived_from(sv, "Data::Histogram::Shared")) \
10             croak("Expected a Data::Histogram::Shared object"); \
11             HistHandle *h = INT2PTR(HistHandle*, SvIV(SvRV(sv))); \
12             if (!h) croak("Attempted to use a destroyed Data::Histogram::Shared object")
13              
14             #define MAKE_OBJ(class, handle) \
15             SV *obj = newSViv(PTR2IV(handle)); \
16             SV *ref = newRV_noinc(obj); \
17             sv_bless(ref, gv_stashpv(class, GV_ADD)); \
18             RETVAL = ref
19              
20             MODULE = Data::Histogram::Shared PACKAGE = Data::Histogram::Shared
21              
22             PROTOTYPES: DISABLE
23              
24             SV *
25             new(class, path = &PL_sv_undef, lowest = 1, highest = 3600000000LL, sig_figs = 3)
26             const char *class
27             SV *path
28             IV lowest
29             IV highest
30             int sig_figs
31             PREINIT:
32             char errbuf[HIST_ERR_BUFLEN];
33             CODE:
34 52 100         const char *p = SvOK(path) ? SvPV_nolen(path) : NULL;
35 52           HistHandle *h = hist_create(p, (int64_t)lowest, (int64_t)highest,
36             (int32_t)sig_figs, errbuf); /* validates args into errbuf */
37 52 100         if (!h) croak("Data::Histogram::Shared->new: %s", errbuf);
38 46           MAKE_OBJ(class, h);
39             OUTPUT:
40             RETVAL
41              
42             SV *
43             new_memfd(class, name = &PL_sv_undef, lowest = 1, highest = 3600000000LL, sig_figs = 3)
44             const char *class
45             SV *name
46             IV lowest
47             IV highest
48             int sig_figs
49             PREINIT:
50             char errbuf[HIST_ERR_BUFLEN];
51             CODE:
52 4 100         const char *nm = SvOK(name) ? SvPV_nolen(name) : NULL; /* undef -> default label */
53 4           HistHandle *h = hist_create_memfd(nm, (int64_t)lowest, (int64_t)highest,
54             (int32_t)sig_figs, errbuf); /* validates args into errbuf */
55 4 100         if (!h) croak("Data::Histogram::Shared->new_memfd: %s", errbuf);
56 2           MAKE_OBJ(class, h);
57             OUTPUT:
58             RETVAL
59              
60             SV *
61             new_from_fd(class, fd)
62             const char *class
63             int fd
64             PREINIT:
65             char errbuf[HIST_ERR_BUFLEN];
66             CODE:
67 2           HistHandle *h = hist_open_fd(fd, errbuf);
68 2 100         if (!h) croak("Data::Histogram::Shared->new_from_fd: %s", errbuf);
69 1           MAKE_OBJ(class, h);
70             OUTPUT:
71             RETVAL
72              
73             void
74             DESTROY(self)
75             SV *self
76             CODE:
77 51 50         if (sv_isobject(self) && sv_derived_from(self, "Data::Histogram::Shared")) {
    50          
78 51           HistHandle *h = INT2PTR(HistHandle*, SvIV(SvRV(self)));
79 51 100         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 */
80             }
81              
82             IV
83             record(self, value, count = 1)
84             SV *self
85             IV value
86             UV count
87             PREINIT:
88 321115 50         EXTRACT(self);
    50          
    100          
89             int64_t idx;
90             IV total;
91             CODE:
92             /* Range-check + index-compute BEFORE locking so a croak holds no lock. */
93 321114 100         if (value < 0)
94 1           croak("Data::Histogram::Shared->record: negative value (%lld)", (long long)value);
95 321113           idx = hist_index_for(h, (int64_t)value);
96 321113 100         if (idx < 0)
97 1           croak("Data::Histogram::Shared->record: value %lld exceeds highest_trackable_value (%lld)",
98             (long long)value, (long long)h->hdr->highest);
99 321112           hist_rwlock_wrlock(h);
100 321112           hist_record_locked(h, (int64_t)value, (int64_t)count);
101 321112           total = (IV)h->hdr->total_count;
102 321112           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
103 321112           hist_rwlock_wrunlock(h);
104 321112 100         RETVAL = total;
105             OUTPUT:
106             RETVAL
107              
108             UV
109             record_many(self, values)
110             SV *self
111             SV *values
112             PREINIT:
113 9 50         EXTRACT(self);
    50          
    50          
114             AV *av;
115             IV top;
116             CODE:
117 9 100         if (!SvROK(values) || SvTYPE(SvRV(values)) != SVt_PVAV)
    100          
118 2           croak("Data::Histogram::Shared->record_many: expected an array reference");
119 7           av = (AV *)SvRV(values);
120 7           top = av_len(av); /* last index, -1 if empty */
121             {
122 7 100         STRLEN cnt = (top >= 0) ? (STRLEN)(top + 1) : 0, i;
123 7           int64_t *vals = NULL;
124 7 100         if (cnt) { /* resolve + range-check ALL before locking */
125 5 50         Newx(vals, cnt, int64_t); SAVEFREEPV(vals); /* freed on return OR unwind */
126 1510 100         for (i = 0; i < cnt; i++) { /* a croak here holds NO lock; SAVEFREEPV cleans up */
127 1508           SV **el = av_fetch(av, (SSize_t)i, 0);
128 1508 50         IV v = (el && *el) ? SvIV(*el) : 0;
    50          
129 1508 100         if (v < 0)
130 1           croak("Data::Histogram::Shared->record_many: negative value (%lld)", (long long)v);
131 1507 100         if (hist_index_for(h, (int64_t)v) < 0)
132 2           croak("Data::Histogram::Shared->record_many: value %lld exceeds highest_trackable_value (%lld)",
133             (long long)v, (long long)h->hdr->highest);
134 1505           vals[i] = (int64_t)v;
135             }
136             }
137 4           hist_rwlock_wrlock(h); /* locked region: NO croak-capable calls */
138 1504 100         for (i = 0; i < cnt; i++) hist_record_locked(h, vals[i], 1);
139 4           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED); /* a call always counts, even an empty batch */
140 4           hist_rwlock_wrunlock(h);
141 4 50         RETVAL = (UV)cnt;
142             }
143             OUTPUT:
144             RETVAL
145              
146             IV
147             value_at_percentile(self, p)
148             SV *self
149             double p
150             PREINIT:
151 39 50         EXTRACT(self);
    50          
    50          
152             IV v;
153             CODE:
154 39           hist_rwlock_rdlock(h);
155 39           v = (IV)hist_value_at_percentile_locked(h, p);
156 39           hist_rwlock_rdunlock(h);
157 39 100         RETVAL = v;
158             OUTPUT:
159             RETVAL
160              
161             IV
162             count_at_value(self, value)
163             SV *self
164             IV value
165             PREINIT:
166 28 50         EXTRACT(self);
    50          
    50          
167             int64_t idx;
168             IV c;
169             CODE:
170 28 100         if (value < 0)
171 1           croak("Data::Histogram::Shared->count_at_value: negative value (%lld)", (long long)value);
172 27           idx = hist_index_for(h, (int64_t)value);
173 27 100         if (idx < 0)
174 1           croak("Data::Histogram::Shared->count_at_value: value %lld exceeds highest_trackable_value (%lld)",
175             (long long)value, (long long)h->hdr->highest);
176 26           hist_rwlock_rdlock(h);
177 26           c = (IV)hist_counts(h)[idx];
178 26           hist_rwlock_rdunlock(h);
179 26 100         RETVAL = c;
180             OUTPUT:
181             RETVAL
182              
183             IV
184             min(self)
185             SV *self
186             PREINIT:
187 9 50         EXTRACT(self);
    50          
    50          
188             int64_t mn, total;
189             CODE:
190 9           hist_rwlock_rdlock(h);
191 9           total = h->hdr->total_count;
192 9           mn = h->hdr->min_value;
193 9           hist_rwlock_rdunlock(h);
194 9 100         RETVAL = (IV)(total == 0 ? 0 : mn);
    50          
195             OUTPUT:
196             RETVAL
197              
198             IV
199             max(self)
200             SV *self
201             PREINIT:
202 10 50         EXTRACT(self);
    50          
    50          
203             IV mx;
204             CODE:
205 10           hist_rwlock_rdlock(h);
206 10           mx = (IV)h->hdr->max_value;
207 10           hist_rwlock_rdunlock(h);
208 10 50         RETVAL = mx;
209             OUTPUT:
210             RETVAL
211              
212             double
213             mean(self)
214             SV *self
215             PREINIT:
216 2 50         EXTRACT(self);
    50          
    50          
217             double m;
218             CODE:
219 2           hist_rwlock_rdlock(h);
220 2           m = hist_mean_locked(h);
221 2           hist_rwlock_rdunlock(h);
222 2 50         RETVAL = m;
223             OUTPUT:
224             RETVAL
225              
226             UV
227             total_count(self)
228             SV *self
229             PREINIT:
230 20 50         EXTRACT(self);
    50          
    50          
231             UV t;
232             CODE:
233 20           hist_rwlock_rdlock(h);
234 20           t = (UV)h->hdr->total_count;
235 20           hist_rwlock_rdunlock(h);
236 20 50         RETVAL = t;
237             OUTPUT:
238             RETVAL
239              
240             void
241             merge(self, other)
242             SV *self
243             SV *other
244             PREINIT:
245 6 50         EXTRACT(self);
    50          
    50          
246             CODE:
247 6 50         if (!sv_isobject(other) || !sv_derived_from(other, "Data::Histogram::Shared"))
    50          
248 0           croak("Data::Histogram::Shared->merge: expected a Data::Histogram::Shared object");
249 6           HistHandle *o = INT2PTR(HistHandle*, SvIV(SvRV(other)));
250 6 50         if (!o) croak("Attempted to use a destroyed Data::Histogram::Shared object");
251              
252             /* Geometry is immutable after creation -- compare lock-free, croak BEFORE
253             * allocating, so a mismatch holds no lock and leaks no buffer. */
254 6 50         if (o->hdr->lowest != h->hdr->lowest ||
255 6 100         o->hdr->highest != h->hdr->highest ||
256 5 100         o->hdr->sig_figs != h->hdr->sig_figs ||
257 4 50         o->hdr->counts_len != h->hdr->counts_len ||
258 4 50         o->hdr->unit_magnitude != h->hdr->unit_magnitude ||
259 4 50         o->hdr->sub_bucket_mask != h->hdr->sub_bucket_mask)
260 2           croak("Data::Histogram::Shared->merge: geometry mismatch "
261             "(lowest=%lld/highest=%lld/sig=%d vs lowest=%lld/highest=%lld/sig=%d)",
262             (long long)h->hdr->lowest, (long long)h->hdr->highest, (int)h->hdr->sig_figs,
263             (long long)o->hdr->lowest, (long long)o->hdr->highest, (int)o->hdr->sig_figs);
264              
265             /* Snapshot the other's counts (+ total/min/max) under its read lock into a
266             * temp buffer, then release before taking self's write lock. Copying to a
267             * temp avoids holding two locks at once (deadlock-free regardless of
268             * acquisition order between two processes merging each other). */
269             {
270 4           int64_t counts_len = h->hdr->counts_len;
271             int64_t other_total, other_min, other_max;
272             int64_t *tmp;
273 4 50         Newx(tmp, (size_t)counts_len, int64_t);
274 4           SAVEFREEPV(tmp); /* freed on normal return OR croak unwind */
275 4           hist_rwlock_rdlock(o);
276 4           memcpy(tmp, hist_counts(o), (size_t)counts_len * sizeof(int64_t));
277 4           other_total = o->hdr->total_count;
278 4           other_min = o->hdr->min_value;
279 4           other_max = o->hdr->max_value;
280 4           hist_rwlock_rdunlock(o);
281              
282 4           hist_rwlock_wrlock(h);
283 4 50         if (other_total > 0) hist_merge_counts(hist_counts(h), tmp, counts_len); /* empty other -> nothing to add */
284 4 50         if (h->hdr->total_count > INT64_MAX - other_total) h->hdr->total_count = INT64_MAX;
285 4           else h->hdr->total_count += other_total;
286 4 50         if (other_total > 0) { /* only adopt min/max if other actually recorded something */
287 4 100         if (other_min < h->hdr->min_value) h->hdr->min_value = other_min;
288 4 100         if (other_max > h->hdr->max_value) h->hdr->max_value = other_max;
289             }
290 4           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
291 4           hist_rwlock_wrunlock(h);
292             }
293              
294             void
295             reset(self)
296             SV *self
297             PREINIT:
298 1 50         EXTRACT(self);
    50          
    50          
299             CODE:
300 1           hist_rwlock_wrlock(h);
301 1           hist_reset_locked(h);
302 1           __atomic_fetch_add(&h->hdr->stat_ops, 1, __ATOMIC_RELAXED);
303 1           hist_rwlock_wrunlock(h);
304              
305             IV
306             lowest(self)
307             SV *self
308             PREINIT:
309 3 50         EXTRACT(self);
    50          
    50          
310             CODE:
311 3 50         RETVAL = (IV)h->hdr->lowest;
312             OUTPUT:
313             RETVAL
314              
315             IV
316             highest(self)
317             SV *self
318             PREINIT:
319 4 50         EXTRACT(self);
    50          
    50          
320             CODE:
321 4 50         RETVAL = (IV)h->hdr->highest;
322             OUTPUT:
323             RETVAL
324              
325             int
326             sig_figs(self)
327             SV *self
328             PREINIT:
329 4 50         EXTRACT(self);
    50          
    50          
330             CODE:
331 4 50         RETVAL = (int)h->hdr->sig_figs;
332             OUTPUT:
333             RETVAL
334              
335             IV
336             counts_len(self)
337             SV *self
338             PREINIT:
339 6 50         EXTRACT(self);
    50          
    50          
340             CODE:
341 6 50         RETVAL = (IV)h->hdr->counts_len;
342             OUTPUT:
343             RETVAL
344              
345             SV *
346             stats(self)
347             SV *self
348             PREINIT:
349 3 50         EXTRACT(self);
    50          
    50          
350             CODE:
351             {
352             int64_t total, mn, mx, counts_len, lowest, highest;
353             int32_t sig_figs, bucket_count, sub_bucket_count;
354             uint64_t ops;
355             double mean;
356             /* Snapshot under the lock; do all (croak-capable) Perl allocation after
357             releasing it -- so an OOM in newHV/newSV* can never strand the lock. */
358 3           hist_rwlock_rdlock(h);
359 3           total = h->hdr->total_count;
360 3           mn = h->hdr->min_value;
361 3           mx = h->hdr->max_value;
362 3           counts_len = h->hdr->counts_len;
363 3           lowest = h->hdr->lowest;
364 3           highest = h->hdr->highest;
365 3           sig_figs = h->hdr->sig_figs;
366 3           bucket_count = h->hdr->bucket_count;
367 3           sub_bucket_count = h->hdr->sub_bucket_count;
368 3           ops = h->hdr->stat_ops;
369 3           mean = hist_mean_locked(h);
370 3           hist_rwlock_rdunlock(h);
371              
372 3           HV *hv = newHV();
373 3           hv_stores(hv, "lowest", newSViv((IV)lowest));
374 3           hv_stores(hv, "highest", newSViv((IV)highest));
375 3           hv_stores(hv, "sig_figs", newSViv((IV)sig_figs));
376 3           hv_stores(hv, "count", newSViv((IV)total));
377 3 50         hv_stores(hv, "min", newSViv((IV)(total == 0 ? 0 : mn)));
378 3           hv_stores(hv, "max", newSViv((IV)mx));
379 3           hv_stores(hv, "mean", newSVnv(mean));
380 3           hv_stores(hv, "counts_len", newSViv((IV)counts_len));
381 3           hv_stores(hv, "bucket_count", newSViv((IV)bucket_count));
382 3           hv_stores(hv, "sub_bucket_count", newSViv((IV)sub_bucket_count));
383 3           hv_stores(hv, "ops", newSVuv((UV)ops));
384 3           hv_stores(hv, "mmap_size", newSVuv((UV)h->mmap_size));
385 3           RETVAL = newRV_noinc((SV *)hv);
386             }
387             OUTPUT:
388             RETVAL
389              
390             SV *
391             path(self)
392             SV *self
393             PREINIT:
394 3 50         EXTRACT(self);
    50          
    50          
395             CODE:
396 3 100         RETVAL = h->path ? newSVpv(h->path, 0) : &PL_sv_undef;
397             OUTPUT:
398             RETVAL
399              
400             int
401             memfd(self)
402             SV *self
403             PREINIT:
404 3 50         EXTRACT(self);
    50          
    50          
405             CODE:
406 3 50         RETVAL = h->backing_fd;
407             OUTPUT:
408             RETVAL
409              
410             void
411             sync(self)
412             SV *self
413             PREINIT:
414 2 50         EXTRACT(self);
    50          
    50          
415             CODE:
416 2 50         if (hist_msync(h) != 0) croak("sync: %s", strerror(errno));
417              
418             void
419             unlink(self, ...)
420             SV *self
421             CODE:
422 3 100         if (sv_isobject(self) && sv_derived_from(self, "Data::Histogram::Shared")) {
    50          
423 1           HistHandle *h = INT2PTR(HistHandle*, SvIV(SvRV(self)));
424 1 50         if (h && h->path) unlink(h->path);
    50          
425 1 50         } else if (items >= 2 && SvOK(ST(1))) {
    50          
426 1           unlink(SvPV_nolen(ST(1)));
427             }