1// Written in the D programming language.
2/**
3Allocator that collects useful statistics about allocations, both global and per
4calling point. The statistics collected can be configured statically by choosing
5combinations of `Options` appropriately.
6
7Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/stats_collector.d)
8*/
9module std.experimental.allocator.building_blocks.stats_collector;
10
11///
12@safe unittest
13{
14    import std.experimental.allocator.gc_allocator : GCAllocator;
15    import std.experimental.allocator.building_blocks.free_list : FreeList;
16    alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed);
17}
18
19import std.experimental.allocator.common;
20
21/**
22_Options for `StatsCollector` defined below. Each enables during
23compilation one specific counter, statistic, or other piece of information.
24*/
25enum Options : ulong
26{
27    /**
28    Counts the number of calls to `owns`.
29    */
30    numOwns = 1u << 0,
31    /**
32    Counts the number of calls to `allocate`. All calls are counted,
33    including requests for zero bytes or failed requests.
34    */
35    numAllocate = 1u << 1,
36    /**
37    Counts the number of calls to `allocate` that succeeded, i.e. they
38    returned a block as large as requested. (N.B. requests for zero bytes count
39    as successful.)
40    */
41    numAllocateOK = 1u << 2,
42    /**
43    Counts the number of calls to `expand`, regardless of arguments or
44    result.
45    */
46    numExpand = 1u << 3,
47    /**
48    Counts the number of calls to `expand` that resulted in a successful
49    expansion.
50    */
51    numExpandOK = 1u << 4,
52    /**
53    Counts the number of calls to `reallocate`, regardless of arguments or
54    result.
55    */
56    numReallocate = 1u << 5,
57    /**
58    Counts the number of calls to `reallocate` that succeeded.
59    (Reallocations to zero bytes count as successful.)
60    */
61    numReallocateOK = 1u << 6,
62    /**
63    Counts the number of calls to `reallocate` that resulted in an in-place
64    reallocation (no memory moved). If this number is close to the total number
65    of reallocations, that indicates the allocator finds room at the current
66    block's end in a large fraction of the cases, but also that internal
67    fragmentation may be high (the size of the unit of allocation is large
68    compared to the typical allocation size of the application).
69    */
70    numReallocateInPlace = 1u << 7,
71    /**
72    Counts the number of calls to `deallocate`.
73    */
74    numDeallocate = 1u << 8,
75    /**
76    Counts the number of calls to `deallocateAll`.
77    */
78    numDeallocateAll = 1u << 9,
79    /**
80    Counts the number of calls to `alignedAllocate`. All calls are counted,
81    including requests for zero bytes or failed requests.
82    */
83    numAlignedAllocate = 1u << 10,
84    /**
85    Counts the number of calls to `alignedAllocate` that succeeded, i.e. they
86    returned a block as large as requested. (N.B. requests for zero bytes count
87    as successful.)
88    */
89    numAlignedAllocateOk = 1u << 11,
90    /**
91    Chooses all `numXxx` flags.
92    */
93    numAll = (1u << 12) - 1,
94    /**
95    Tracks bytes currently allocated by this allocator. This number goes up
96    and down as memory is allocated and deallocated, and is zero if the
97    allocator currently has no active allocation.
98    */
99    bytesUsed = 1u << 12,
100    /**
101    Tracks total cumulative bytes allocated by means of `allocate`,
102    `expand`, and `reallocate` (when resulting in an expansion). This
103    number always grows and indicates allocation traffic. To compute bytes
104    deallocated cumulatively, subtract `bytesUsed` from `bytesAllocated`.
105    */
106    bytesAllocated = 1u << 13,
107    /**
108    Tracks the sum of all `delta` values in calls of the form
109    $(D expand(b, delta)) that succeed (return `true`).
110    */
111    bytesExpanded = 1u << 14,
112    /**
113    Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of
114    the form $(D realloc(b, s)) that succeed (return `true`). In per-call
115    statistics, also unambiguously counts the bytes deallocated with
116    `deallocate`.
117    */
118    bytesContracted = 1u << 15,
119    /**
120    Tracks the sum of all bytes moved as a result of calls to `realloc` that
121    were unable to reallocate in place. A large number (relative to $(D
122    bytesAllocated)) indicates that the application should use larger
123    preallocations.
124    */
125    bytesMoved = 1u << 16,
126    /**
127    Tracks the sum of all bytes NOT moved as result of calls to `realloc`
128    that managed to reallocate in place. A large number (relative to $(D
129    bytesAllocated)) indicates that the application is expansion-intensive and
130    is saving a good amount of moves. However, if this number is relatively
131    small and `bytesSlack` is high, it means the application is
132    overallocating for little benefit.
133    */
134    bytesNotMoved = 1u << 17,
135    /**
136    Measures the sum of extra bytes allocated beyond the bytes requested, i.e.
137    the $(HTTP goo.gl/YoKffF, internal fragmentation). This is the current
138    effective number of slack bytes, and it goes up and down with time.
139    */
140    bytesSlack = 1u << 18,
141    /**
142    Measures the maximum bytes allocated over the time. This is useful for
143    dimensioning allocators.
144    */
145    bytesHighTide = 1u << 19,
146    /**
147    Chooses all `byteXxx` flags.
148    */
149    bytesAll = ((1u << 20) - 1) & ~numAll,
150    /**
151    Combines all flags above.
152    */
153    all = (1u << 20) - 1
154}
155
156/**
157
158Allocator that collects extra data about allocations. Since each piece of
159information adds size and time overhead, statistics can be individually enabled
160or disabled through compile-time `flags`.
161
162All stats of the form `numXxx` record counts of events occurring, such as
163calls to functions and specific results. The stats of the form `bytesXxx`
164collect cumulative sizes.
165
166In addition, the data `callerSize`, `callerModule`, `callerFile`, $(D
167callerLine), and `callerTime` is associated with each specific allocation.
168This data prefixes each allocation.
169
170*/
171struct StatsCollector(Allocator, ulong flags = Options.all,
172    ulong perCallFlags = 0)
173{
174private:
175    import std.traits : hasMember, Signed;
176    import std.typecons : Ternary;
177
178    static string define(string type, string[] names...)
179    {
180        string result;
181        foreach (v; names)
182            result ~= "static if (flags & Options."~v~") {"
183                ~ "private "~type~" _"~v~";"
184                ~ "public const("~type~") "~v~"() const { return _"~v~"; }"
185                ~ "}";
186        return result;
187    }
188
189    void add(string counter)(Signed!size_t n)
190    {
191        mixin("static if (flags & Options." ~ counter
192            ~ ") _" ~ counter ~ " += n;");
193        static if (counter == "bytesUsed" && (flags & Options.bytesHighTide))
194        {
195            if (bytesHighTide < bytesUsed ) _bytesHighTide = bytesUsed;
196        }
197    }
198
199    void up(string counter)() { add!counter(1); }
200    void down(string counter)() { add!counter(-1); }
201
202    version (StdDdoc)
203    {
204        /**
205        Read-only properties enabled by the homonym `flags` chosen by the
206        user.
207
208        Example:
209        ----
210        StatsCollector!(Mallocator,
211            Options.bytesUsed | Options.bytesAllocated) a;
212        auto d1 = a.allocate(10);
213        auto d2 = a.allocate(11);
214        a.deallocate(d1);
215        assert(a.bytesAllocated == 21);
216        assert(a.bytesUsed == 11);
217        a.deallocate(d2);
218        assert(a.bytesAllocated == 21);
219        assert(a.bytesUsed == 0);
220        ----
221        */
222        @property ulong numOwns() const;
223        /// Ditto
224        @property ulong numAllocate() const;
225        /// Ditto
226        @property ulong numAllocateOK() const;
227        /// Ditto
228        @property ulong numExpand() const;
229        /// Ditto
230        @property ulong numExpandOK() const;
231        /// Ditto
232        @property ulong numReallocate() const;
233        /// Ditto
234        @property ulong numReallocateOK() const;
235        /// Ditto
236        @property ulong numReallocateInPlace() const;
237        /// Ditto
238        @property ulong numDeallocate() const;
239        /// Ditto
240        @property ulong numDeallocateAll() const;
241        /// Ditto
242        @property ulong numAlignedAllocate() const;
243        /// Ditto
244        @property ulong numAlignedAllocateOk() const;
245        /// Ditto
246        @property ulong bytesUsed() const;
247        /// Ditto
248        @property ulong bytesAllocated() const;
249        /// Ditto
250        @property ulong bytesExpanded() const;
251        /// Ditto
252        @property ulong bytesContracted() const;
253        /// Ditto
254        @property ulong bytesMoved() const;
255        /// Ditto
256        @property ulong bytesNotMoved() const;
257        /// Ditto
258        @property ulong bytesSlack() const;
259        /// Ditto
260        @property ulong bytesHighTide() const;
261    }
262
263public:
264    /**
265    The parent allocator is publicly accessible either as a direct member if it
266    holds state, or as an alias to `Allocator.instance` otherwise. One may use
267    it for making calls that won't count toward statistics collection.
268    */
269    static if (stateSize!Allocator) Allocator parent;
270    else alias parent = Allocator.instance;
271
272private:
273    // Per-allocator state
274    mixin(define("ulong",
275        "numOwns",
276        "numAllocate",
277        "numAllocateOK",
278        "numExpand",
279        "numExpandOK",
280        "numReallocate",
281        "numReallocateOK",
282        "numReallocateInPlace",
283        "numDeallocate",
284        "numDeallocateAll",
285        "numAlignedAllocate",
286        "numAlignedAllocateOk",
287        "bytesUsed",
288        "bytesAllocated",
289        "bytesExpanded",
290        "bytesContracted",
291        "bytesMoved",
292        "bytesNotMoved",
293        "bytesSlack",
294        "bytesHighTide",
295    ));
296
297public:
298
299    /// Alignment offered is equal to `Allocator.alignment`.
300    alias alignment = Allocator.alignment;
301
302    /**
303    Increments `numOwns` (per instance and and per call) and forwards to $(D
304    parent.owns(b)).
305    */
306    static if (hasMember!(Allocator, "owns"))
307    {
308        static if ((perCallFlags & Options.numOwns) == 0)
309        Ternary owns(void[] b)
310        { return ownsImpl(b); }
311        else
312        Ternary owns(string f = __FILE__, uint n = __LINE__)(void[] b)
313        { return ownsImpl!(f, n)(b); }
314    }
315
316    private Ternary ownsImpl(string f = null, uint n = 0)(void[] b)
317    {
318        up!"numOwns";
319        addPerCall!(f, n, "numOwns")(1);
320        return parent.owns(b);
321    }
322
323    /**
324    Forwards to `parent.allocate`. Affects per instance: `numAllocate`,
325    `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAllocateOK`,
326    and `bytesHighTide`. Affects per call: `numAllocate`, $(D
327    numAllocateOK), and `bytesAllocated`.
328    */
329    static if (!(perCallFlags
330        & (Options.numAllocate | Options.numAllocateOK
331            | Options.bytesAllocated)))
332    {
333        void[] allocate(size_t n)
334        { return allocateImpl(n); }
335    }
336    else
337    {
338        void[] allocate(string f = __FILE__, ulong n = __LINE__)
339            (size_t bytes)
340        { return allocateImpl!(f, n)(bytes); }
341    }
342
343    // Common code currently shared between allocateImpl and allocateZeroedImpl.
344    private enum _updateStatsForAllocateResult =
345    q{
346        add!"bytesUsed"(result.length);
347        add!"bytesAllocated"(result.length);
348        immutable slack = this.goodAllocSize(result.length) - result.length;
349        add!"bytesSlack"(slack);
350        up!"numAllocate";
351        add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK
352        addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated")
353            (1, result.length == bytes, result.length);
354    };
355
356    private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes)
357    {
358        auto result = parent.allocate(bytes);
359        mixin(_updateStatsForAllocateResult);
360        return result;
361    }
362
363    static if (hasMember!(Allocator, "allocateZeroed"))
364    {
365        static if (!(perCallFlags
366            & (Options.numAllocate | Options.numAllocateOK
367                | Options.bytesAllocated)))
368        {
369            package(std) void[] allocateZeroed()(size_t n)
370            { return allocateZeroedImpl(n); }
371        }
372        else
373        {
374            package(std) void[] allocateZeroed(string f = __FILE__, ulong n = __LINE__)
375                (size_t bytes)
376            { return allocateZeroedImpl!(f, n)(bytes); }
377        }
378
379        private void[] allocateZeroedImpl(string f = null, ulong n = 0)(size_t bytes)
380        {
381            auto result = parent.allocateZeroed(bytes);
382            // Note: calls to `allocateZeroed` are counted for statistical purposes
383            // as if they were calls to `allocate`. If/when `allocateZeroed` is made
384            // public it might be of interest to count such calls separately.
385            mixin(_updateStatsForAllocateResult);
386            return result;
387        }
388    }
389
390    /**
391    Forwards to `parent.alignedAllocate`. Affects per instance: `numAlignedAllocate`,
392    `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAlignedAllocateOk`,
393    and `bytesHighTide`. Affects per call: `numAlignedAllocate`, `numAlignedAllocateOk`,
394    and `bytesAllocated`.
395    */
396    static if (!(perCallFlags
397        & (Options.numAlignedAllocate | Options.numAlignedAllocateOk
398            | Options.bytesAllocated)))
399    {
400        void[] alignedAllocate(size_t n, uint a)
401        { return alignedAllocateImpl(n, a); }
402    }
403    else
404    {
405        void[] alignedAllocate(string f = __FILE__, ulong n = __LINE__)
406            (size_t bytes, uint a)
407        { return alignedAllocateImpl!(f, n)(bytes, a); }
408    }
409
410    private void[] alignedAllocateImpl(string f = null, ulong n = 0)(size_t bytes, uint a)
411    {
412        up!"numAlignedAllocate";
413        static if (!hasMember!(Allocator, "alignedAllocate"))
414        {
415            if (bytes == 0)
416                up!"numAlignedAllocateOk";
417            void[] result = null;
418        }
419        else
420        {
421            auto result = parent.alignedAllocate(bytes, a);
422            add!"bytesUsed"(result.length);
423            add!"bytesAllocated"(result.length);
424            immutable slack = this.goodAllocSize(result.length) - result.length;
425            add!"bytesSlack"(slack);
426            add!"numAlignedAllocateOk"(result.length == bytes); // allocating 0 bytes is OK
427        }
428        addPerCall!(f, n, "numAlignedAllocate", "numAlignedAllocateOk", "bytesAllocated")
429            (1, result.length == bytes, result.length);
430
431        return result;
432    }
433
434    /**
435    Defined whether or not `Allocator.expand` is defined. Affects
436    per instance: `numExpand`, `numExpandOK`, `bytesExpanded`,
437    `bytesSlack`, `bytesAllocated`, and `bytesUsed`. Affects per call:
438    `numExpand`, `numExpandOK`, `bytesExpanded`, and
439    `bytesAllocated`.
440    */
441    static if (!(perCallFlags
442        & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded)))
443    {
444        bool expand(ref void[] b, size_t delta)
445        { return expandImpl(b, delta); }
446    }
447    else
448    {
449        bool expand(string f = __FILE__, uint n = __LINE__)
450            (ref void[] b, size_t delta)
451        { return expandImpl!(f, n)(b, delta); }
452    }
453
454    private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s)
455    {
456        up!"numExpand";
457        Signed!size_t slack = 0;
458        static if (!hasMember!(Allocator, "expand"))
459        {
460            auto result = s == 0;
461        }
462        else
463        {
464            immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
465            auto result = parent.expand(b, s);
466            if (result)
467            {
468                up!"numExpandOK";
469                add!"bytesUsed"(s);
470                add!"bytesAllocated"(s);
471                add!"bytesExpanded"(s);
472                slack = Signed!size_t(this.goodAllocSize(b.length) - b.length
473                    - bytesSlackB4);
474                add!"bytesSlack"(slack);
475            }
476        }
477        immutable xtra = result ? s : 0;
478        addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded",
479            "bytesAllocated")
480            (1, result, xtra, xtra);
481        return result;
482    }
483
484    /**
485    Defined whether or not `Allocator.reallocate` is defined. Affects
486    per instance: `numReallocate`, `numReallocateOK`, $(D
487    numReallocateInPlace), `bytesNotMoved`, `bytesAllocated`, $(D
488    bytesSlack), `bytesExpanded`, and `bytesContracted`. Affects per call:
489    `numReallocate`, `numReallocateOK`, `numReallocateInPlace`,
490    `bytesNotMoved`, `bytesExpanded`, `bytesContracted`, and
491    `bytesMoved`.
492    */
493    static if (!(perCallFlags
494        & (Options.numReallocate | Options.numReallocateOK
495            | Options.numReallocateInPlace | Options.bytesNotMoved
496            | Options.bytesExpanded | Options.bytesContracted
497            | Options.bytesMoved)))
498    {
499        bool reallocate(ref void[] b, size_t s)
500        { return reallocateImpl(b, s); }
501    }
502    else
503    {
504        bool reallocate(string f = __FILE__, ulong n = __LINE__)
505            (ref void[] b, size_t s)
506        { return reallocateImpl!(f, n)(b, s); }
507    }
508
509    private bool reallocateImpl(string f = null, uint n = 0)
510        (ref void[] b, size_t s)
511    {
512        up!"numReallocate";
513        const bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
514        const oldB = b.ptr;
515        const oldLength = b.length;
516
517        const result = parent.reallocate(b, s);
518
519        Signed!size_t slack = 0;
520        bool wasInPlace = false;
521        Signed!size_t delta = 0;
522
523        if (result)
524        {
525            up!"numReallocateOK";
526            slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4;
527            add!"bytesSlack"(slack);
528            add!"bytesUsed"(Signed!size_t(b.length - oldLength));
529            if (oldB == b.ptr)
530            {
531                // This was an in-place reallocation, yay
532                wasInPlace = true;
533                up!"numReallocateInPlace";
534                add!"bytesNotMoved"(oldLength);
535                delta = b.length - oldLength;
536                if (delta >= 0)
537                {
538                    // Expansion
539                    add!"bytesAllocated"(delta);
540                    add!"bytesExpanded"(delta);
541                }
542                else
543                {
544                    // Contraction
545                    add!"bytesContracted"(-delta);
546                }
547            }
548            else
549            {
550                // This was a allocate-move-deallocate cycle
551                add!"bytesAllocated"(b.length);
552                add!"bytesMoved"(oldLength);
553            }
554        }
555        addPerCall!(f, n, "numReallocate", "numReallocateOK",
556            "numReallocateInPlace", "bytesNotMoved",
557            "bytesExpanded", "bytesContracted", "bytesMoved")
558            (1, result, wasInPlace, wasInPlace ? oldLength : 0,
559                delta >= 0 ? delta : 0, delta < 0 ? -delta : 0,
560                wasInPlace ? 0 : oldLength);
561        return result;
562    }
563
564    /**
565    Defined whether or not `Allocator.deallocate` is defined. Affects
566    per instance: `numDeallocate`, `bytesUsed`, and `bytesSlack`.
567    Affects per call: `numDeallocate` and `bytesContracted`.
568    */
569    static if (!(perCallFlags &
570            (Options.numDeallocate | Options.bytesContracted)))
571        bool deallocate(void[] b)
572        { return deallocateImpl(b); }
573    else
574        bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b)
575        { return deallocateImpl!(f, n)(b); }
576
577    private bool deallocateImpl(string f = null, uint n = 0)(void[] b)
578    {
579        up!"numDeallocate";
580        add!"bytesUsed"(-Signed!size_t(b.length));
581        add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length));
582        addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length);
583        static if (hasMember!(Allocator, "deallocate"))
584            return parent.deallocate(b);
585        else
586            return false;
587    }
588
589    static if (hasMember!(Allocator, "deallocateAll"))
590    {
591        /**
592        Defined only if `Allocator.deallocateAll` is defined. Affects
593        per instance and per call `numDeallocateAll`.
594        */
595        static if (!(perCallFlags & Options.numDeallocateAll))
596            bool deallocateAll()
597            { return deallocateAllImpl(); }
598        else
599            bool deallocateAll(string f = __FILE__, uint n = __LINE__)()
600            { return deallocateAllImpl!(f, n)(); }
601
602        private bool deallocateAllImpl(string f = null, uint n = 0)()
603        {
604            up!"numDeallocateAll";
605            addPerCall!(f, n, "numDeallocateAll")(1);
606            static if ((flags & Options.bytesUsed))
607                _bytesUsed = 0;
608            return parent.deallocateAll();
609        }
610    }
611
612    /**
613    Defined only if `Options.bytesUsed` is defined. Returns $(D bytesUsed ==
614    0).
615    */
616    static if (flags & Options.bytesUsed)
617    pure nothrow @safe @nogc
618    Ternary empty()
619    {
620        return Ternary(_bytesUsed == 0);
621    }
622
623    /**
624    Reports per instance statistics to `output` (e.g. `stdout`). The
625    format is simple: one kind and value per line, separated by a colon, e.g.
626    `bytesAllocated:7395404`
627    */
628    void reportStatistics(R)(auto ref R output)
629    {
630        import std.conv : to;
631        import std.traits : EnumMembers;
632        foreach (e; EnumMembers!Options)
633        {
634            static if ((flags & e) && e != Options.numAll
635                    && e != Options.bytesAll && e != Options.all)
636                output.write(e.to!string, ":", mixin(e.to!string), '\n');
637        }
638    }
639
640    static if (perCallFlags)
641    {
642        /**
643        Defined if `perCallFlags` is nonzero.
644        */
645        struct PerCallStatistics
646        {
647            /// The file and line of the call.
648            string file;
649            /// Ditto
650            uint line;
651            /// The options corresponding to the statistics collected.
652            Options[] opts;
653            /// The values of the statistics. Has the same length as `opts`.
654            ulong[] values;
655            // Next in the chain.
656            private PerCallStatistics* next;
657
658            /**
659            Format to a string such as:
660            $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]).
661            */
662            string toString() const
663            {
664                import std.conv : text, to;
665                auto result = text(file, "(", line, "): [");
666                foreach (i, opt; opts)
667                {
668                    if (i) result ~= ", ";
669                    result ~= opt.to!string;
670                    result ~= ':';
671                    result ~= values[i].to!string;
672                }
673                return result ~= "]";
674            }
675        }
676        private static PerCallStatistics* root;
677
678        /**
679        Defined if `perCallFlags` is nonzero. Iterates all monitored
680        file/line instances. The order of iteration is not meaningful (items
681        are inserted at the front of a list upon the first call), so
682        preprocessing the statistics after collection might be appropriate.
683        */
684        static auto byFileLine()
685        {
686            static struct Voldemort
687            {
688                PerCallStatistics* current;
689                bool empty() { return !current; }
690                ref PerCallStatistics front() { return *current; }
691                void popFront() { current = current.next; }
692                auto save() { return this; }
693            }
694            return Voldemort(root);
695        }
696
697        /**
698        Defined if `perCallFlags` is nonzero. Outputs (e.g. to a `File`)
699        a simple report of the collected per-call statistics.
700        */
701        static void reportPerCallStatistics(R)(auto ref R output)
702        {
703            output.write("Stats for: ", StatsCollector.stringof, '\n');
704            foreach (ref stat; byFileLine)
705            {
706                output.write(stat, '\n');
707            }
708        }
709
710        private PerCallStatistics* statsAt(string f, uint n, opts...)()
711        {
712            import std.array : array;
713            import std.range : repeat;
714
715            static PerCallStatistics s = { f, n, [ opts ],
716                repeat(0UL, opts.length).array };
717            static bool inserted;
718
719            if (!inserted)
720            {
721                // Insert as root
722                s.next = root;
723                root = &s;
724                inserted = true;
725            }
726            return &s;
727        }
728
729        private void addPerCall(string f, uint n, names...)(ulong[] values...)
730        {
731            import std.array : join;
732            enum ulong mask = mixin("Options."~[names].join("|Options."));
733            static if (perCallFlags & mask)
734            {
735                // Per allocation info
736                auto ps = mixin("statsAt!(f, n,"
737                    ~ "Options."~[names].join(", Options.")
738                ~")");
739                foreach (i; 0 .. names.length)
740                {
741                    ps.values[i] += values[i];
742                }
743            }
744        }
745    }
746    else
747    {
748        private void addPerCall(string f, uint n, names...)(ulong[]...)
749        {
750        }
751    }
752}
753
754///
755@system unittest
756{
757    import std.experimental.allocator.building_blocks.free_list : FreeList;
758    import std.experimental.allocator.gc_allocator : GCAllocator;
759    alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all);
760
761    Allocator alloc;
762    auto b = alloc.allocate(10);
763    alloc.reallocate(b, 20);
764    alloc.deallocate(b);
765
766    import std.file : deleteme, remove;
767    import std.range : walkLength;
768    import std.stdio : File;
769
770    auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt";
771    scope(exit) remove(f);
772    Allocator.reportPerCallStatistics(File(f, "w"));
773    alloc.reportStatistics(File(f, "a"));
774    assert(File(f).byLine.walkLength == 24);
775}
776
777@system unittest
778{
779    void test(Allocator)()
780    {
781        import std.range : walkLength;
782        import std.typecons : Ternary;
783
784        Allocator a;
785        assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes);
786        auto b1 = a.allocate(100);
787        assert(a.numAllocate == 1);
788        assert((() nothrow @safe => a.expand(b1, 0))());
789        assert(a.reallocate(b1, b1.length + 1));
790        auto b2 = a.allocate(101);
791        assert(a.numAllocate == 2);
792        assert(a.bytesAllocated == 202);
793        assert(a.bytesUsed == 202);
794        auto b3 = a.allocate(202);
795        assert(a.numAllocate == 3);
796        assert(a.bytesAllocated == 404);
797        assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no);
798
799        () nothrow @nogc { a.deallocate(b2); }();
800        assert(a.numDeallocate == 1);
801        () nothrow @nogc { a.deallocate(b1); }();
802        assert(a.numDeallocate == 2);
803        () nothrow @nogc { a.deallocate(b3); }();
804        assert(a.numDeallocate == 3);
805        assert(a.numAllocate == a.numDeallocate);
806        assert(a.bytesUsed == 0);
807     }
808
809    import std.experimental.allocator.building_blocks.free_list : FreeList;
810    import std.experimental.allocator.gc_allocator : GCAllocator;
811    test!(StatsCollector!(GCAllocator, Options.all, Options.all));
812    test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all,
813        Options.all));
814}
815
816@system unittest
817{
818    void test(Allocator)()
819    {
820        import std.range : walkLength;
821        Allocator a;
822        auto b1 = a.allocate(100);
823        assert((() nothrow @safe => a.expand(b1, 0))());
824        assert(a.reallocate(b1, b1.length + 1));
825        auto b2 = a.allocate(101);
826        auto b3 = a.allocate(202);
827
828        () nothrow @nogc { a.deallocate(b2); }();
829        () nothrow @nogc { a.deallocate(b1); }();
830        () nothrow @nogc { a.deallocate(b3); }();
831    }
832    import std.experimental.allocator.building_blocks.free_list : FreeList;
833    import std.experimental.allocator.gc_allocator : GCAllocator;
834    test!(StatsCollector!(GCAllocator, 0, 0));
835}
836
837@system unittest
838{
839    import std.experimental.allocator.gc_allocator : GCAllocator;
840    StatsCollector!(GCAllocator, 0, 0) a;
841
842    // calls std.experimental.allocator.common.goodAllocSize
843    assert((() pure nothrow @safe @nogc => a.goodAllocSize(1))());
844}
845
846@system unittest
847{
848    import std.experimental.allocator.building_blocks.region : Region;
849
850    auto a = StatsCollector!(Region!(), Options.all, Options.all)(Region!()(new ubyte[1024 * 64]));
851    auto b = a.allocate(42);
852    assert(b.length == 42);
853    // Test that reallocate infers from parent
854    assert((() nothrow @nogc => a.reallocate(b, 100))());
855    assert(b.length == 100);
856    // Test that deallocateAll infers from parent
857    assert((() nothrow @nogc => a.deallocateAll())());
858}
859
860@system unittest
861{
862    import std.experimental.allocator.building_blocks.region : Region;
863
864    auto a = StatsCollector!(Region!(), Options.all)(Region!()(new ubyte[1024 * 64]));
865    auto b = a.alignedAllocate(42, 128);
866    assert(b.length == 42);
867    assert(b.ptr.alignedAt(128));
868    assert(a.numAlignedAllocate == 1);
869    assert(a.numAlignedAllocateOk == 1);
870    assert(a.bytesUsed == 42);
871
872    b = a.alignedAllocate(23, 256);
873    assert(b.length == 23);
874    assert(b.ptr.alignedAt(256));
875    assert(a.numAlignedAllocate == 2);
876    assert(a.numAlignedAllocateOk == 2);
877    assert(a.bytesUsed == 65);
878
879    b = a.alignedAllocate(0, 512);
880    assert(b.length == 0);
881    assert(a.numAlignedAllocate == 3);
882    assert(a.numAlignedAllocateOk == 3);
883    assert(a.bytesUsed == 65);
884
885    b = a.alignedAllocate(1024 * 1024, 512);
886    assert(b is null);
887    assert(a.numAlignedAllocate == 4);
888    assert(a.numAlignedAllocateOk == 3);
889    assert(a.bytesUsed == 65);
890}
891