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