"Fossies" - the Fresh Open Source Software Archive

Member "dmd2/src/phobos/std/experimental/allocator/building_blocks/stats_collector.d" (20 Nov 2020, 29568 Bytes) of package /linux/misc/dmd.2.094.2.linux.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) D source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/stats_collector.d)
    8 */
    9 module 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 
   19 import std.experimental.allocator.common;
   20 
   21 /**
   22 _Options for `StatsCollector` defined below. Each enables during
   23 compilation one specific counter, statistic, or other piece of information.
   24 */
   25 enum 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 
  158 Allocator that collects extra data about allocations. Since each piece of
  159 information adds size and time overhead, statistics can be individually enabled
  160 or disabled through compile-time `flags`.
  161 
  162 All stats of the form `numXxx` record counts of events occurring, such as
  163 calls to functions and specific results. The stats of the form `bytesXxx`
  164 collect cumulative sizes.
  165 
  166 In addition, the data `callerSize`, `callerModule`, `callerFile`, $(D
  167 callerLine), and `callerTime` is associated with each specific allocation.
  168 This data prefixes each allocation.
  169 
  170 */
  171 struct StatsCollector(Allocator, ulong flags = Options.all,
  172     ulong perCallFlags = 0)
  173 {
  174 private:
  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 
  263 public:
  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 
  272 private:
  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 
  297 public:
  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 }