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 }