1 /** 2 * This module contains garbage collector statistics functionality. 3 * 4 * Copyright: Copyright (C) 2005-2006 Digital Mars, www.digitalmars.com. 5 * All rights reserved. 6 * License: 7 * This software is provided 'as-is', without any express or implied 8 * warranty. In no event will the authors be held liable for any damages 9 * arising from the use of this software. 10 * 11 * Permission is granted to anyone to use this software for any purpose, 12 * including commercial applications, and to alter it and redistribute it 13 * freely, in both source and binary form, subject to the following 14 * restrictions: 15 * 16 * o The origin of this software must not be misrepresented; you must not 17 * claim that you wrote the original software. If you use this software 18 * in a product, an acknowledgment in the product documentation would be 19 * appreciated but is not required. 20 * o Altered source versions must be plainly marked as such, and must not 21 * be misrepresented as being the original software. 22 * o This notice may not be removed or altered from any source 23 * distribution. 24 * Authors: Walter Bright, Sean Kelly, Leandro Lucarella 25 */ 26 27 module rt.gc.cdgc.stats; 28 29 import gc = rt.gc.cdgc.gc; 30 import rt.gc.cdgc.bits: GCBits; 31 import rt.gc.cdgc.opts: options; 32 33 import cstdio = tango.stdc.stdio; 34 import ctime = tango.stdc.posix.sys.time; 35 36 37 private: 38 39 40 /** 41 * Time information for a collection. 42 * 43 * This struct groups all the timing information for a particular collection. 44 * It stores how much time it took the different parts of a collection, splat 45 * in: allocation time (the time the mutator spent in the malloc() call that 46 * triggered the collection), time the world was stopped because of the 47 * collection and the time spent in the collection itself. The time the world 48 * was stopped includes the time spent in the collection (in this 49 * implementation, which is not concurrent) and the time spent in the 50 * allocation includes the time the world was stopped (this is probably true 51 * for any implementation). 52 */ 53 struct TimeInfo 54 { 55 /// Collection time (in seconds). 56 double collection = -1.0; 57 /// Stop-the-world time (in seconds). 58 double stop_the_world = -1.0; 59 /// Time spent in the malloc that triggered the collection (in seconds). 60 double malloc = -1.0; 61 } 62 63 64 /** 65 * Memory (space) information for a collection. 66 * 67 * This struct groups all the space information for a particular collection. 68 * The space is partitioned in four: used, free, wasted and overhead. "used" is 69 * the net space needed by the mutator; "free" is the space the GC has ready to 70 * be given to the mutator when needed; "wasted" is memory that can't be used 71 * by neither the collector nor the mutator (usually because of fragmentation) 72 * and "overhead" is the space needed by the GC itself. 73 */ 74 struct MemoryInfo 75 { 76 /// Heap memory used by the program (in bytes). 77 size_t used = 0; 78 /// Free heap memory (in bytes). 79 size_t free = 0; 80 /// Memory that can't be used at all (in bytes). 81 size_t wasted = 0; 82 /// Memory used by the GC for bookkeeping (in bytes). 83 size_t overhead = 0; 84 } 85 86 87 /** 88 * Information about a particular collection. 89 * 90 * This struct groups all information related to a particular collection. The 91 * timings for the collection as stored and the space requirements are both, 92 * before and after that collection, logged to easily measure how effective 93 * the collection was in terms of space. The time when this collection was 94 * triggered is logged too (relative to the program start, in seconds). 95 * 96 * See_Also: TimeInfo and MemoryInfo structs. 97 */ 98 struct CollectionInfo 99 { 100 /// When this information were taken (seconds since the program started). 101 double timestamp = -1.0; 102 /// Time statistics for this collection. 103 TimeInfo time; 104 /// Memory statistics before the collection. 105 MemoryInfo before; 106 /// Memory statistics after the collection. 107 MemoryInfo after; 108 } 109 110 111 /** 112 * Information about a particular allocation. 113 * 114 * This struct groups all the information about a particular allocation. The 115 * size requested in that allocation is logged, as well as the attributes 116 * assigned to that cell, the time malloc() took to complete and if 117 * a collection was triggered. The time when this allocation was requested is 118 * logged too (relative to the program start, in seconds). 119 */ 120 struct MallocInfo 121 { 122 /// When this information were taken (seconds since the program started). 123 double timestamp = -1.0; 124 /// Time spent in the malloc() call (in seconds). 125 double time = -1.0; 126 /// Address of the pointer returned by malloc. 127 void* ptr = null; 128 /// Memory requested by the malloc() call (in bytes). 129 size_t size = 0; 130 /// Memory attributes as BlkAttr flags. 131 uint attr = 0; 132 /// True if this malloc() triggered a collection. 133 bool collected = false; 134 /// Address of the pointer map bitmask. 135 size_t* ptrmap = null; 136 } 137 138 139 package: 140 141 /** 142 * Control and store the GC statistics. 143 * 144 * This is the interface to this module, it has methods for the GC to inform 145 * when a relevant event has happened. The events are the start and finish of 146 * an allocation, when the world is stopped and restarted and when 147 * a collection is triggered and done. 148 * 149 * All the data is logged in memory and printed to the standard output when 150 * requested (usually at the end of the program). 151 * 152 * See_Also: CollectionInfo and MallocInfo structs. 153 */ 154 struct Stats 155 { 156 157 private: 158 159 /// The GC instance we are collecting stats from. 160 .gc.GC* gc = null; 161 162 /// True if statistics should be collected. 163 bool active = false; 164 165 /// Current collection information (printed when the malloc finishes). 166 MallocInfo malloc_info; 167 168 /// File where to write the malloc information to. 169 cstdio.FILE* mallocs_file; 170 171 /// Current collection information (printed when the collection finishes). 172 CollectionInfo collection_info; 173 174 /// File where to write the collections information to. 175 cstdio.FILE* collections_file; 176 177 /// Time when the program started. 178 double program_start = -1.0; 179 180 /// Return the current time as seconds since the epoch. 181 static double now() 182 { 183 ctime.timeval tv; 184 ctime.gettimeofday(&tv, null); 185 return cast(double) tv.tv_sec + cast(double) tv.tv_usec / 1_000_000.0; 186 } 187 188 /// Fill a MemoryInfo struct with the current state of the GC heap. 189 void fill_memory_info(MemoryInfo* mem_info) 190 { 191 mem_info.overhead += .gc.GC.sizeof + gc.pools.elements_sizeof + 192 gc.roots.elements_sizeof + gc.ranges.elements_sizeof; 193 194 // pools 195 for (size_t i = 0; i < gc.pools.length; i++) 196 { 197 auto pool = gc.pools[i]; 198 mem_info.overhead += pool.npages * ubyte.sizeof; 199 // the 5 bitmaps (mark, scan, free, final, noscan) 200 mem_info.overhead += 5 * (GCBits.sizeof 201 + (pool.mark.nwords + 2) * uint.sizeof); 202 203 for (size_t pn = 0; pn < pool.npages; pn++) 204 { 205 auto bin = cast(.gc.Bins) pool.pagetable[pn]; 206 if (bin < .gc.B_PAGE) 207 { 208 size_t size = .gc.binsize[bin]; 209 size_t attrstride = size / 16; 210 size_t attrbase = pn * (.gc.PAGESIZE / 16); 211 size_t attrtop = attrbase + (.gc.PAGESIZE / 16); 212 for (auto attri = attrbase; attri < attrtop; attri += attrstride) 213 { 214 if (pool.freebits.test(attri)) 215 mem_info.free += size; 216 else 217 mem_info.used += size; // TODO: wasted 218 } 219 } 220 else if (bin == .gc.B_FREE) 221 mem_info.free += .gc.PAGESIZE; 222 else // B_PAGE / B_PAGEPLUS 223 mem_info.used += .gc.PAGESIZE; // TODO: wasted 224 } 225 } 226 } 227 228 cstdio.FILE* start_file(char* filename, char* header) 229 { 230 cstdio.FILE* file = cstdio.fopen(filename, "w"); 231 if (file !is null) 232 cstdio.fputs(header, file); 233 return file; 234 } 235 236 void print_malloc() 237 { 238 if (this.mallocs_file is null) 239 return; 240 auto ptrmap = this.malloc_info.ptrmap; 241 auto ptrmask_offset = (ptrmap[0] - 1) / (size_t.sizeof * 8) + 1; 242 cstdio.fprintf(this.mallocs_file, 243 "%f,%f,%p,%zu,%zu,%zu,%zu,%zu,%p,%zu,%0*zX,%0*zX\n", 244 //0 1 2 3 4 5 6 7 8 9 10 11 245 this.malloc_info.timestamp, // 0 246 this.malloc_info.time, // 1 247 this.malloc_info.ptr, // 2 248 this.malloc_info.size, // 3 249 this.malloc_info.collected ? 1u : 0u, // 4 250 this.malloc_info.attr & .gc.BlkAttr.FINALIZE, // 5 251 this.malloc_info.attr & .gc.BlkAttr.NO_SCAN, // 6 252 this.malloc_info.attr & .gc.BlkAttr.NO_MOVE, // 7 253 ptrmap, // 8 254 ptrmap[0] * size_t.sizeof, // 9 255 size_t.sizeof, // fill length 256 ptrmap[1], // 10 257 size_t.sizeof, // fill length 258 ptrmap[1 + ptrmask_offset]); // 11 259 // TODO: make it an option 260 cstdio.fflush(this.mallocs_file); 261 } 262 263 void print_collection() 264 { 265 if (this.collections_file is null) 266 return; 267 cstdio.fprintf(this.collections_file, 268 "%f,%f,%f,%f,%zu,%zu,%zu,%zu,%zu,%zu,%zu,%zu\n", 269 //0 1 2 3 4 5 6 7 8 9 10 11 270 this.collection_info.timestamp, // 0 271 this.collection_info.time.malloc, // 1 272 this.collection_info.time.collection, // 2 273 this.collection_info.time.stop_the_world, // 3 274 this.collection_info.before.used, // 4 275 this.collection_info.before.free, // 5 276 this.collection_info.before.wasted, // 6 277 this.collection_info.before.overhead, // 7 278 this.collection_info.after.used, // 8 279 this.collection_info.after.free, // 9 280 this.collection_info.after.wasted, // 10 281 this.collection_info.after.overhead); // 11 282 // TODO: make it an option 283 cstdio.fflush(this.collections_file); 284 } 285 286 287 public: 288 289 /** 290 * Construct a Stats object (useful for easy initialization). 291 * 292 * This function should be always used to initialize a Stats object because 293 * the program start time (in seconds since the epoch) needs to be taken to 294 * properly add timestamps to allocations and collections. 295 */ 296 static Stats opCall(.gc.GC* gc) 297 { 298 Stats this_; 299 this_.gc = gc; 300 if (options.malloc_stats_file[0] != '\0') { 301 this_.mallocs_file = this_.start_file( 302 options.malloc_stats_file.ptr, 303 "Timestamp,Time,Pointer,Size,Collection triggered," 304 "Finalize,No scan,No move,Pointer map,Type size," 305 "Pointer map scan bitmask (first word, hexa)," 306 "Pointer map pointer bitmask (first word, hexa)\n"); 307 if (this_.mallocs_file !is null) 308 this_.active = true; 309 } 310 // collection 311 if (options.collect_stats_file[0] != '\0') { 312 this_.collections_file = this_.start_file( 313 options.collect_stats_file.ptr, 314 "Timestamp,Malloc time,Collection time,Stop-the-world time," 315 "Used before,Free before,Wasted before,Overhead before," 316 "Used after,Free after,Wasted after,Overhead after\n"); 317 if (this_.collections_file !is null) 318 this_.active = true; 319 } 320 this_.program_start = this_.now(); 321 return this_; 322 } 323 324 void finalize() 325 { 326 if (this.mallocs_file !is null) 327 cstdio.fclose(this.mallocs_file); 328 if (this.collections_file !is null) 329 cstdio.fclose(this.collections_file); 330 } 331 332 /// Inform the start of an allocation. 333 // TODO: store/use type information 334 void malloc_started(size_t size, uint attr, size_t* ptrmap_bitmask) 335 { 336 if (!this.active) 337 return; 338 auto now = this.now(); 339 auto relative_now = now - this.program_start; 340 // malloc 341 this.malloc_info = this.malloc_info.init; 342 this.malloc_info.timestamp = relative_now; 343 this.malloc_info.time = now; 344 this.malloc_info.size = size; 345 this.malloc_info.attr = attr; 346 this.malloc_info.ptrmap = ptrmap_bitmask; 347 // this.malloc_info.collected is filled in malloc_finished() 348 // collection 349 this.collection_info = this.collection_info.init; 350 this.collection_info.timestamp = relative_now; 351 // this.collection_info.time.malloc is the same as malloc_info.time 352 } 353 354 /// Inform the end of an allocation. 355 void malloc_finished(void* ptr) 356 { 357 if (!this.active) 358 return; 359 auto now = this.now(); 360 auto collected = !(this.collection_info.time.collection < 0.0); 361 // malloc 362 this.malloc_info.time = now - this.malloc_info.time; 363 if (collected) 364 this.malloc_info.collected = true; 365 this.malloc_info.ptr = ptr; 366 this.print_malloc(); 367 if (!collected) 368 return; 369 // collection 370 this.collection_info.time.malloc = this.malloc_info.time; 371 this.print_collection(); 372 } 373 374 /// Inform that all threads (the world) have been stopped. 375 void world_stopped() 376 { 377 if (!this.active) 378 return; 379 this.collection_info.time.stop_the_world = this.now(); 380 } 381 382 /// Inform that all threads (the world) have been resumed. 383 void world_started() 384 { 385 if (!this.active) 386 return; 387 this.collection_info.time.stop_the_world = 388 this.now() - this.collection_info.time.stop_the_world; 389 } 390 391 /// Inform the start of a collection. 392 void collection_started() 393 { 394 if (!this.active) 395 return; 396 this.fill_memory_info(&this.collection_info.before); 397 this.collection_info.time.collection = this.now(); 398 } 399 400 /// Inform the end of a collection. 401 void collection_finished() 402 { 403 if (!this.active) 404 return; 405 this.collection_info.time.collection = 406 this.now() - this.collection_info.time.collection; 407 this.fill_memory_info(&this.collection_info.after); 408 } 409 410 } 411 412 413 /** 414 * 415 */ 416 struct GCStats 417 { 418 size_t poolsize; // total size of pool 419 size_t usedsize; // bytes allocated 420 size_t freeblocks; // number of blocks marked FREE 421 size_t freelistsize; // total of memory on free lists 422 size_t pageblocks; // number of blocks marked PAGE 423 } 424 425 426 // vim: set et sw=4 sts=4 :