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