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 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 313 Ternary owns(string f = __FILE__, uint n = __LINE__)(void[] b) 314 { return ownsImpl!(f, n)(b); } 315 } 316 317 private Ternary ownsImpl(string f = null, uint n = 0)(void[] b) 318 { 319 up!"numOwns"; 320 addPerCall!(f, n, "numOwns")(1); 321 return parent.owns(b); 322 } 323 324 /** 325 Forwards to `parent.allocate`. Affects per instance: `numAllocate`, 326 `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAllocateOK`, 327 and `bytesHighTide`. Affects per call: `numAllocate`, $(D 328 numAllocateOK), and `bytesAllocated`. 329 */ 330 static if (!(perCallFlags 331 & (Options.numAllocate | Options.numAllocateOK 332 | Options.bytesAllocated))) 333 { 334 void[] allocate(size_t n) 335 { return allocateImpl(n); } 336 } 337 else 338 { 339 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 340 void[] allocate(string f = __FILE__, ulong n = __LINE__) 341 (size_t bytes) 342 { return allocateImpl!(f, n)(bytes); } 343 } 344 345 // Common code currently shared between allocateImpl and allocateZeroedImpl. 346 private enum _updateStatsForAllocateResult = 347 q{ 348 add!"bytesUsed"(result.length); 349 add!"bytesAllocated"(result.length); 350 immutable slack = this.goodAllocSize(result.length) - result.length; 351 add!"bytesSlack"(slack); 352 up!"numAllocate"; 353 add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK 354 addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated") 355 (1, result.length == bytes, result.length); 356 }; 357 358 private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes) 359 { 360 auto result = parent.allocate(bytes); 361 mixin(_updateStatsForAllocateResult); 362 return result; 363 } 364 365 static if (hasMember!(Allocator, "allocateZeroed")) 366 { 367 static if (!(perCallFlags 368 & (Options.numAllocate | Options.numAllocateOK 369 | Options.bytesAllocated))) 370 { 371 package(std) void[] allocateZeroed()(size_t n) 372 { return allocateZeroedImpl(n); } 373 } 374 else 375 { 376 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 377 package(std) void[] allocateZeroed(string f = __FILE__, ulong n = __LINE__) 378 (size_t bytes) 379 { return allocateZeroedImpl!(f, n)(bytes); } 380 } 381 382 private void[] allocateZeroedImpl(string f = null, ulong n = 0)(size_t bytes) 383 { 384 auto result = parent.allocateZeroed(bytes); 385 // Note: calls to `allocateZeroed` are counted for statistical purposes 386 // as if they were calls to `allocate`. If/when `allocateZeroed` is made 387 // public it might be of interest to count such calls separately. 388 mixin(_updateStatsForAllocateResult); 389 return result; 390 } 391 } 392 393 /** 394 Forwards to `parent.alignedAllocate`. Affects per instance: `numAlignedAllocate`, 395 `bytesUsed`, `bytesAllocated`, `bytesSlack`, `numAlignedAllocateOk`, 396 and `bytesHighTide`. Affects per call: `numAlignedAllocate`, `numAlignedAllocateOk`, 397 and `bytesAllocated`. 398 */ 399 static if (!(perCallFlags 400 & (Options.numAlignedAllocate | Options.numAlignedAllocateOk 401 | Options.bytesAllocated))) 402 { 403 void[] alignedAllocate(size_t n, uint a) 404 { return alignedAllocateImpl(n, a); } 405 } 406 else 407 { 408 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 409 void[] alignedAllocate(string f = __FILE__, ulong n = __LINE__) 410 (size_t bytes, uint a) 411 { return alignedAllocateImpl!(f, n)(bytes, a); } 412 } 413 414 private void[] alignedAllocateImpl(string f = null, ulong n = 0)(size_t bytes, uint a) 415 { 416 up!"numAlignedAllocate"; 417 static if (!hasMember!(Allocator, "alignedAllocate")) 418 { 419 if (bytes == 0) 420 up!"numAlignedAllocateOk"; 421 void[] result = null; 422 } 423 else 424 { 425 auto result = parent.alignedAllocate(bytes, a); 426 add!"bytesUsed"(result.length); 427 add!"bytesAllocated"(result.length); 428 immutable slack = this.goodAllocSize(result.length) - result.length; 429 add!"bytesSlack"(slack); 430 add!"numAlignedAllocateOk"(result.length == bytes); // allocating 0 bytes is OK 431 } 432 addPerCall!(f, n, "numAlignedAllocate", "numAlignedAllocateOk", "bytesAllocated") 433 (1, result.length == bytes, result.length); 434 435 return result; 436 } 437 438 /** 439 Defined whether or not `Allocator.expand` is defined. Affects 440 per instance: `numExpand`, `numExpandOK`, `bytesExpanded`, 441 `bytesSlack`, `bytesAllocated`, and `bytesUsed`. Affects per call: 442 `numExpand`, `numExpandOK`, `bytesExpanded`, and 443 `bytesAllocated`. 444 */ 445 static if (!(perCallFlags 446 & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded))) 447 { 448 bool expand(ref void[] b, size_t delta) 449 { return expandImpl(b, delta); } 450 } 451 else 452 { 453 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 454 bool expand(string f = __FILE__, uint n = __LINE__) 455 (ref void[] b, size_t delta) 456 { return expandImpl!(f, n)(b, delta); } 457 } 458 459 private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s) 460 { 461 up!"numExpand"; 462 Signed!size_t slack = 0; 463 static if (!hasMember!(Allocator, "expand")) 464 { 465 auto result = s == 0; 466 } 467 else 468 { 469 immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length; 470 auto result = parent.expand(b, s); 471 if (result) 472 { 473 up!"numExpandOK"; 474 add!"bytesUsed"(s); 475 add!"bytesAllocated"(s); 476 add!"bytesExpanded"(s); 477 slack = Signed!size_t(this.goodAllocSize(b.length) - b.length 478 - bytesSlackB4); 479 add!"bytesSlack"(slack); 480 } 481 } 482 immutable xtra = result ? s : 0; 483 addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded", 484 "bytesAllocated") 485 (1, result, xtra, xtra); 486 return result; 487 } 488 489 /** 490 Defined whether or not `Allocator.reallocate` is defined. Affects 491 per instance: `numReallocate`, `numReallocateOK`, $(D 492 numReallocateInPlace), `bytesNotMoved`, `bytesAllocated`, $(D 493 bytesSlack), `bytesExpanded`, and `bytesContracted`. Affects per call: 494 `numReallocate`, `numReallocateOK`, `numReallocateInPlace`, 495 `bytesNotMoved`, `bytesExpanded`, `bytesContracted`, and 496 `bytesMoved`. 497 */ 498 static if (!(perCallFlags 499 & (Options.numReallocate | Options.numReallocateOK 500 | Options.numReallocateInPlace | Options.bytesNotMoved 501 | Options.bytesExpanded | Options.bytesContracted 502 | Options.bytesMoved))) 503 { 504 bool reallocate(ref void[] b, size_t s) 505 { return reallocateImpl(b, s); } 506 } 507 else 508 { 509 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 510 bool reallocate(string f = __FILE__, ulong n = __LINE__) 511 (ref void[] b, size_t s) 512 { return reallocateImpl!(f, n)(b, s); } 513 } 514 515 private bool reallocateImpl(string f = null, uint n = 0) 516 (ref void[] b, size_t s) 517 { 518 up!"numReallocate"; 519 const bytesSlackB4 = this.goodAllocSize(b.length) - b.length; 520 const oldB = b.ptr; 521 const oldLength = b.length; 522 523 const result = parent.reallocate(b, s); 524 525 Signed!size_t slack = 0; 526 bool wasInPlace = false; 527 Signed!size_t delta = 0; 528 529 if (result) 530 { 531 up!"numReallocateOK"; 532 slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4; 533 add!"bytesSlack"(slack); 534 add!"bytesUsed"(Signed!size_t(b.length - oldLength)); 535 if (oldB == b.ptr) 536 { 537 // This was an in-place reallocation, yay 538 wasInPlace = true; 539 up!"numReallocateInPlace"; 540 add!"bytesNotMoved"(oldLength); 541 delta = b.length - oldLength; 542 if (delta >= 0) 543 { 544 // Expansion 545 add!"bytesAllocated"(delta); 546 add!"bytesExpanded"(delta); 547 } 548 else 549 { 550 // Contraction 551 add!"bytesContracted"(-delta); 552 } 553 } 554 else 555 { 556 // This was a allocate-move-deallocate cycle 557 add!"bytesAllocated"(b.length); 558 add!"bytesMoved"(oldLength); 559 } 560 } 561 addPerCall!(f, n, "numReallocate", "numReallocateOK", 562 "numReallocateInPlace", "bytesNotMoved", 563 "bytesExpanded", "bytesContracted", "bytesMoved") 564 (1, result, wasInPlace, wasInPlace ? oldLength : 0, 565 delta >= 0 ? delta : 0, delta < 0 ? -delta : 0, 566 wasInPlace ? 0 : oldLength); 567 return result; 568 } 569 570 /** 571 Defined whether or not `Allocator.deallocate` is defined. Affects 572 per instance: `numDeallocate`, `bytesUsed`, and `bytesSlack`. 573 Affects per call: `numDeallocate` and `bytesContracted`. 574 */ 575 static if (!(perCallFlags & 576 (Options.numDeallocate | Options.bytesContracted))) 577 bool deallocate(void[] b) 578 { return deallocateImpl(b); } 579 else 580 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 581 bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b) 582 { return deallocateImpl!(f, n)(b); } 583 584 private bool deallocateImpl(string f = null, uint n = 0)(void[] b) 585 { 586 up!"numDeallocate"; 587 add!"bytesUsed"(-Signed!size_t(b.length)); 588 add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length)); 589 addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length); 590 static if (hasMember!(Allocator, "deallocate")) 591 return parent.deallocate(b); 592 else 593 return false; 594 } 595 596 static if (hasMember!(Allocator, "deallocateAll")) 597 { 598 /** 599 Defined only if `Allocator.deallocateAll` is defined. Affects 600 per instance and per call `numDeallocateAll`. 601 */ 602 static if (!(perCallFlags & Options.numDeallocateAll)) 603 bool deallocateAll() 604 { return deallocateAllImpl(); } 605 else 606 pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter 607 bool deallocateAll(string f = __FILE__, uint n = __LINE__)() 608 { return deallocateAllImpl!(f, n)(); } 609 610 private bool deallocateAllImpl(string f = null, uint n = 0)() 611 { 612 up!"numDeallocateAll"; 613 addPerCall!(f, n, "numDeallocateAll")(1); 614 static if ((flags & Options.bytesUsed)) 615 _bytesUsed = 0; 616 return parent.deallocateAll(); 617 } 618 } 619 620 /** 621 Defined only if `Options.bytesUsed` is defined. Returns $(D bytesUsed == 622 0). 623 */ 624 static if (flags & Options.bytesUsed) 625 pure nothrow @safe @nogc 626 Ternary empty() 627 { 628 return Ternary(_bytesUsed == 0); 629 } 630 631 /** 632 Reports per instance statistics to `output` (e.g. `stdout`). The 633 format is simple: one kind and value per line, separated by a colon, e.g. 634 `bytesAllocated:7395404` 635 */ 636 void reportStatistics(R)(auto ref R output) 637 { 638 import std.conv : to; 639 import std.traits : EnumMembers; 640 foreach (e; EnumMembers!Options) 641 { 642 static if ((flags & e) && e != Options.numAll 643 && e != Options.bytesAll && e != Options.all) 644 output.write(e.to!string, ":", mixin(e.to!string), '\n'); 645 } 646 } 647 648 static if (perCallFlags) 649 { 650 /** 651 Defined if `perCallFlags` is nonzero. 652 */ 653 struct PerCallStatistics 654 { 655 /// The file and line of the call. 656 string file; 657 /// Ditto 658 uint line; 659 /// The options corresponding to the statistics collected. 660 Options[] opts; 661 /// The values of the statistics. Has the same length as `opts`. 662 ulong[] values; 663 // Next in the chain. 664 private PerCallStatistics* next; 665 666 /** 667 Format to a string such as: 668 $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]). 669 */ 670 string toString() const 671 { 672 import std.conv : text, to; 673 auto result = text(file, "(", line, "): ["); 674 foreach (i, opt; opts) 675 { 676 if (i) result ~= ", "; 677 result ~= opt.to!string; 678 result ~= ':'; 679 result ~= values[i].to!string; 680 } 681 return result ~= "]"; 682 } 683 } 684 private static PerCallStatistics* root; 685 686 /** 687 Defined if `perCallFlags` is nonzero. Iterates all monitored 688 file/line instances. The order of iteration is not meaningful (items 689 are inserted at the front of a list upon the first call), so 690 preprocessing the statistics after collection might be appropriate. 691 */ 692 static auto byFileLine() 693 { 694 static struct Voldemort 695 { 696 PerCallStatistics* current; 697 bool empty() { return !current; } 698 ref PerCallStatistics front() { return *current; } 699 void popFront() { current = current.next; } 700 auto save() { return this; } 701 } 702 return Voldemort(root); 703 } 704 705 /** 706 Defined if `perCallFlags` is nonzero. Outputs (e.g. to a `File`) 707 a simple report of the collected per-call statistics. 708 */ 709 static void reportPerCallStatistics(R)(auto ref R output) 710 { 711 output.write("Stats for: ", StatsCollector.stringof, '\n'); 712 foreach (ref stat; byFileLine) 713 { 714 output.write(stat, '\n'); 715 } 716 } 717 718 private PerCallStatistics* statsAt(string f, uint n, opts...)() 719 { 720 import std.array : array; 721 import std.range : repeat; 722 723 static PerCallStatistics s = { f, n, [ opts ], 724 repeat(0UL, opts.length).array }; 725 static bool inserted; 726 727 if (!inserted) 728 { 729 // Insert as root 730 s.next = root; 731 root = &s; 732 inserted = true; 733 } 734 return &s; 735 } 736 737 private void addPerCall(string f, uint n, names...)(ulong[] values...) 738 { 739 import std.array : join; 740 enum ulong mask = mixin("Options."~[names].join("|Options.")); 741 static if (perCallFlags & mask) 742 { 743 // Per allocation info 744 auto ps = mixin("statsAt!(f, n," 745 ~ "Options."~[names].join(", Options.") 746 ~")"); 747 foreach (i; 0 .. names.length) 748 { 749 ps.values[i] += values[i]; 750 } 751 } 752 } 753 } 754 else 755 { 756 private void addPerCall(string f, uint n, names...)(ulong[]...) 757 { 758 } 759 } 760 } 761 762 /// 763 @system unittest 764 { 765 import std.experimental.allocator.building_blocks.free_list : FreeList; 766 import std.experimental.allocator.gc_allocator : GCAllocator; 767 alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all); 768 769 Allocator alloc; 770 auto b = alloc.allocate(10); 771 alloc.reallocate(b, 20); 772 alloc.deallocate(b); 773 774 import std.file : deleteme, remove; 775 import std.range : walkLength; 776 import std.stdio : File; 777 778 auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt"; 779 scope(exit) remove(f); 780 Allocator.reportPerCallStatistics(File(f, "w")); 781 alloc.reportStatistics(File(f, "a")); 782 assert(File(f).byLine.walkLength == 24); 783 } 784 785 @system unittest 786 { 787 void test(Allocator)() 788 { 789 import std.range : walkLength; 790 import std.typecons : Ternary; 791 792 Allocator a; 793 assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); 794 auto b1 = a.allocate(100); 795 assert(a.numAllocate == 1); 796 assert((() nothrow @safe => a.expand(b1, 0))()); 797 assert(a.reallocate(b1, b1.length + 1)); 798 auto b2 = a.allocate(101); 799 assert(a.numAllocate == 2); 800 assert(a.bytesAllocated == 202); 801 assert(a.bytesUsed == 202); 802 auto b3 = a.allocate(202); 803 assert(a.numAllocate == 3); 804 assert(a.bytesAllocated == 404); 805 assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); 806 807 () nothrow @nogc { a.deallocate(b2); }(); 808 assert(a.numDeallocate == 1); 809 () nothrow @nogc { a.deallocate(b1); }(); 810 assert(a.numDeallocate == 2); 811 () nothrow @nogc { a.deallocate(b3); }(); 812 assert(a.numDeallocate == 3); 813 assert(a.numAllocate == a.numDeallocate); 814 assert(a.bytesUsed == 0); 815 } 816 817 import std.experimental.allocator.building_blocks.free_list : FreeList; 818 import std.experimental.allocator.gc_allocator : GCAllocator; 819 test!(StatsCollector!(GCAllocator, Options.all, Options.all)); 820 test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all, 821 Options.all)); 822 } 823 824 @system unittest 825 { 826 void test(Allocator)() 827 { 828 import std.range : walkLength; 829 Allocator a; 830 auto b1 = a.allocate(100); 831 assert((() nothrow @safe => a.expand(b1, 0))()); 832 assert(a.reallocate(b1, b1.length + 1)); 833 auto b2 = a.allocate(101); 834 auto b3 = a.allocate(202); 835 836 () nothrow @nogc { a.deallocate(b2); }(); 837 () nothrow @nogc { a.deallocate(b1); }(); 838 () nothrow @nogc { a.deallocate(b3); }(); 839 } 840 import std.experimental.allocator.building_blocks.free_list : FreeList; 841 import std.experimental.allocator.gc_allocator : GCAllocator; 842 test!(StatsCollector!(GCAllocator, 0, 0)); 843 } 844 845 @system unittest 846 { 847 import std.experimental.allocator.gc_allocator : GCAllocator; 848 StatsCollector!(GCAllocator, 0, 0) a; 849 850 // calls std.experimental.allocator.common.goodAllocSize 851 assert((() pure nothrow @safe @nogc => a.goodAllocSize(1))()); 852 } 853 854 @system unittest 855 { 856 import std.experimental.allocator.building_blocks.region : BorrowedRegion; 857 858 auto a = StatsCollector!(BorrowedRegion!(), Options.all, Options.all)(BorrowedRegion!()(new ubyte[1024 * 64])); 859 auto b = a.allocate(42); 860 assert(b.length == 42); 861 // Test that reallocate infers from parent 862 assert((() nothrow @nogc => a.reallocate(b, 100))()); 863 assert(b.length == 100); 864 // Test that deallocateAll infers from parent 865 assert((() nothrow @nogc => a.deallocateAll())()); 866 } 867 868 @system unittest 869 { 870 import std.experimental.allocator.building_blocks.region : BorrowedRegion; 871 872 auto a = StatsCollector!(BorrowedRegion!(), Options.all)(BorrowedRegion!()(new ubyte[1024 * 64])); 873 auto b = a.alignedAllocate(42, 128); 874 assert(b.length == 42); 875 assert(b.ptr.alignedAt(128)); 876 assert(a.numAlignedAllocate == 1); 877 assert(a.numAlignedAllocateOk == 1); 878 assert(a.bytesUsed == 42); 879 880 b = a.alignedAllocate(23, 256); 881 assert(b.length == 23); 882 assert(b.ptr.alignedAt(256)); 883 assert(a.numAlignedAllocate == 2); 884 assert(a.numAlignedAllocateOk == 2); 885 assert(a.bytesUsed == 65); 886 887 b = a.alignedAllocate(0, 512); 888 assert(b.length == 0); 889 assert(a.numAlignedAllocate == 3); 890 assert(a.numAlignedAllocateOk == 3); 891 assert(a.bytesUsed == 65); 892 893 b = a.alignedAllocate(1024 * 1024, 512); 894 assert(b is null); 895 assert(a.numAlignedAllocate == 4); 896 assert(a.numAlignedAllocateOk == 3); 897 assert(a.bytesUsed == 65); 898 }