1 /* 2 * Data collection and report generation for 3 * -profile=gc 4 * switch 5 * 6 * Copyright: Copyright Digital Mars 2015 - 2015. 7 * License: Distributed under the 8 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 9 * (See accompanying file LICENSE) 10 * Authors: Andrei Alexandrescu and Walter Bright 11 * Source: $(DRUNTIMESRC rt/_profilegc.d) 12 */ 13 14 module rt.profilegc; 15 16 private: 17 18 import core.stdc.stdio; 19 import core.stdc.stdlib; 20 import core.stdc.string; 21 22 import core.exception : onOutOfMemoryError; 23 import core.internal.container.hashtab; 24 25 struct Entry { ulong count, size; } 26 27 char[] buffer; 28 HashTab!(const(char)[], Entry) newCounts; 29 30 __gshared 31 { 32 HashTab!(const(char)[], Entry) globalNewCounts; 33 string logfilename = "profilegc.log"; 34 } 35 36 /**** 37 * Set file name for output. 38 * A file name of "" means write results to stdout. 39 * Params: 40 * name = file name 41 */ 42 43 extern (C) void profilegc_setlogfilename(string name) 44 { 45 logfilename = name ~ "\0"; 46 } 47 48 public void accumulate(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow 49 { 50 if (sz == 0) 51 return; 52 53 char[3 * line.sizeof + 1] buf = void; 54 auto buflen = snprintf(buf.ptr, buf.length, "%u", line); 55 56 auto length = type.length + 1 + funcname.length + 1 + file.length + 1 + buflen; 57 if (length > buffer.length) 58 { 59 // Enlarge buffer[] so it is big enough 60 assert(buffer.length > 0 || buffer.ptr is null); 61 auto p = cast(char*)realloc(buffer.ptr, length); 62 if (!p) 63 onOutOfMemoryError(); 64 buffer = p[0 .. length]; 65 } 66 67 // "type funcname file:line" 68 buffer[0 .. type.length] = type[]; 69 buffer[type.length] = ' '; 70 buffer[type.length + 1 .. 71 type.length + 1 + funcname.length] = funcname[]; 72 buffer[type.length + 1 + funcname.length] = ' '; 73 buffer[type.length + 1 + funcname.length + 1 .. 74 type.length + 1 + funcname.length + 1 + file.length] = file[]; 75 buffer[type.length + 1 + funcname.length + 1 + file.length] = ':'; 76 buffer[type.length + 1 + funcname.length + 1 + file.length + 1 .. 77 type.length + 1 + funcname.length + 1 + file.length + 1 + buflen] = buf[0 .. buflen]; 78 79 if (auto pcount = cast(string)buffer[0 .. length] in newCounts) 80 { // existing entry 81 pcount.count++; 82 pcount.size += sz; 83 } 84 else 85 { 86 auto key = (cast(char*) malloc(char.sizeof * length))[0 .. length]; 87 key[] = buffer[0..length]; 88 newCounts[key] = Entry(1, sz); // new entry 89 } 90 } 91 92 // Merge thread local newCounts into globalNewCounts 93 static ~this() 94 { 95 if (newCounts.length) 96 { 97 synchronized 98 { 99 foreach (name, entry; newCounts) 100 { 101 if (!(name in globalNewCounts)) 102 globalNewCounts[name] = Entry.init; 103 104 globalNewCounts[name].count += entry.count; 105 globalNewCounts[name].size += entry.size; 106 } 107 } 108 newCounts.reset(); 109 } 110 free(buffer.ptr); 111 buffer = null; 112 } 113 114 // Write report to stderr 115 shared static ~this() 116 { 117 static struct Result 118 { 119 const(char)[] name; 120 Entry entry; 121 122 // qsort() comparator to sort by count field 123 extern (C) static int qsort_cmp(scope const void *r1, scope const void *r2) @nogc nothrow 124 { 125 auto result1 = cast(Result*)r1; 126 auto result2 = cast(Result*)r2; 127 long cmp = result2.entry.size - result1.entry.size; 128 if (cmp) return cmp < 0 ? -1 : 1; 129 cmp = result2.entry.count - result1.entry.count; 130 if (cmp) return cmp < 0 ? -1 : 1; 131 if (result2.name == result1.name) return 0; 132 // ascending order for names reads better 133 return result2.name > result1.name ? -1 : 1; 134 } 135 } 136 137 size_t size = globalNewCounts.length; 138 Result[] counts = (cast(Result*) malloc(size * Result.sizeof))[0 .. size]; 139 scope(exit) 140 free(counts.ptr); 141 142 size_t i; 143 foreach (name, entry; globalNewCounts) 144 { 145 counts[i].name = name; 146 counts[i].entry = entry; 147 ++i; 148 } 149 150 if (counts.length) 151 { 152 qsort(counts.ptr, counts.length, Result.sizeof, &Result.qsort_cmp); 153 154 FILE* fp = logfilename.length == 0 ? stdout : fopen((logfilename).ptr, "w"); 155 if (fp) 156 { 157 fprintf(fp, "bytes allocated, allocations, type, function, file:line\n"); 158 foreach (ref c; counts) 159 { 160 fprintf(fp, "%15llu\t%15llu\t%8.*s\n", 161 cast(ulong)c.entry.size, cast(ulong)c.entry.count, 162 cast(int) c.name.length, c.name.ptr); 163 } 164 if (logfilename.length) 165 fclose(fp); 166 } 167 else 168 fprintf(stderr, "cannot write profilegc log file '%.*s'", cast(int) logfilename.length, logfilename.ptr); 169 } 170 } 171