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 $(HTTPS en.wikipedia.org/wiki/Fragmentation_(computing)#Internal_fragmentation, 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 : BorrowedRegion; 849 850 auto a = StatsCollector!(BorrowedRegion!(), Options.all, Options.all)(BorrowedRegion!()(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 : BorrowedRegion; 863 864 auto a = StatsCollector!(BorrowedRegion!(), Options.all)(BorrowedRegion!()(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 }