1 module served.workers.profilegc; 2 3 import std.algorithm; 4 import std.array; 5 import std.ascii : isDigit; 6 import std.conv; 7 import std.experimental.logger; 8 import std.format; 9 import std.json; 10 import std.string; 11 12 import served.types; 13 14 import workspaced.api; 15 16 import painlessjson; 17 18 struct ProfileGCEntry 19 { 20 size_t bytesAllocated; 21 size_t allocationCount; 22 string type; 23 string uri, displayFile; 24 uint line; 25 } 26 27 struct ProfileGCCache 28 { 29 struct PerDocumentCache 30 { 31 ProfileGCEntry[] entries; 32 33 auto process(DocumentUri relativeToUri, scope const(char)[] content) 34 { 35 entries = null; 36 foreach (line; content.lineSplitter) 37 { 38 auto cols = line.split; 39 if (cols.length < 5) 40 continue; 41 auto typeStart = cols[2].ptr - line.ptr; 42 if (typeStart < 0 || typeStart > line.length) 43 typeStart = 0; 44 auto fileStart = line.lastIndexOfAny(" \t"); 45 if (fileStart != -1) 46 { 47 fileStart++; 48 auto colon = line.indexOf(":", fileStart); 49 if (colon != -1 && line[colon + 1 .. $].strip.all!isDigit) 50 { 51 auto file = line[fileStart .. colon]; 52 auto lineNo = line[colon + 1 .. $].strip.to!uint; 53 entries.assumeSafeAppend ~= ProfileGCEntry( 54 cols[0].to!size_t, 55 cols[1].to!size_t, 56 line[typeStart .. fileStart - 1].strip.idup, 57 uriBuildNormalized(relativeToUri, file), 58 file.idup, 59 lineNo 60 ); 61 } 62 } 63 } 64 return entries; 65 } 66 } 67 68 PerDocumentCache[DocumentUri] caches; 69 70 void update(DocumentUri uri) 71 { 72 import std.file : FileException; 73 74 try 75 { 76 auto profileGC = documents.getOrFromFilesystem(uri); 77 trace("Processing profilegc.log ", uri); 78 auto entries = caches.require(uri).process(uri.uriDirName, profileGC.rawText); 79 // trace("Processed: ", entries); 80 } 81 catch (FileException e) 82 { 83 trace("File Exception processing profilegc: ", e.msg); 84 caches.remove(uri); 85 } 86 catch (Exception e) 87 { 88 trace("Exception processing profilegc: ", e); 89 caches.remove(uri); 90 } 91 } 92 93 void clear(DocumentUri uri) 94 { 95 trace("Clearing profilegc.log cache from ", uri); 96 caches.remove(uri); 97 } 98 } 99 100 package __gshared ProfileGCCache profileGCCache; 101 102 @protocolMethod("textDocument/codeLens") 103 CodeLens[] provideProfileGCCodeLens(CodeLensParams params) 104 { 105 if (!config(params.textDocument.uri).d.enableGCProfilerDecorations) 106 return null; 107 108 auto lenses = appender!(CodeLens[]); 109 foreach (url, cache; profileGCCache.caches) 110 { 111 foreach (entry; cache.entries) 112 { 113 if (entry.uri == params.textDocument.uri) 114 { 115 lenses ~= CodeLens( 116 TextRange(entry.line - 1, 0, entry.line - 1, 1), 117 Command(format!"%s bytes allocated / %s allocations"(entry.bytesAllocated, entry.allocationCount)).opt 118 ); 119 } 120 } 121 } 122 return lenses.data; 123 } 124 125 @protocolMethod("served/getProfileGCEntries") 126 ProfileGCEntry[] getProfileGCEntries() 127 { 128 auto lenses = appender!(ProfileGCEntry[]); 129 foreach (url, cache; profileGCCache.caches) 130 lenses ~= cache.entries; 131 return lenses.data; 132 } 133 134 @onRegisteredComponents 135 void setupProfileGCWatchers() 136 { 137 if (capabilities.workspace.didChangeWatchedFiles.dynamicRegistration) 138 { 139 rpc.sendRequest("client/registerCapability", 140 RegistrationParams([ 141 Registration( 142 "profilegc.watchfiles", 143 "workspace/didChangeWatchedFiles", 144 JSONValue([ 145 "watchers": JSONValue([ 146 FileSystemWatcher("**/profilegc.log").toJSON 147 ]) 148 ]) 149 ) 150 ]) 151 ); 152 } 153 } 154 155 @onProjectAvailable 156 void onProfileGCProjectAvailable(WorkspaceD.Instance instance, string dir, string uri) 157 { 158 profileGCCache.update(uri.chomp("/") ~ "/profilegc.log"); 159 } 160 161 @protocolNotification("workspace/didChangeWatchedFiles") 162 void onChangeProfileGC(DidChangeWatchedFilesParams params) 163 { 164 foreach (change; params.changes) 165 { 166 if (!change.uri.endsWith("profilegc.log")) 167 continue; 168 169 if (change.type == FileChangeType.created 170 || change.type == FileChangeType.changed) 171 profileGCCache.update(change.uri); 172 else if (change.type == FileChangeType.deleted) 173 profileGCCache.clear(change.uri); 174 } 175 }