1 | //===--- Server.cpp - gRPC-based Remote Index Server ---------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "Feature.h" |
10 | #include "Index.pb.h" |
11 | #include "MonitoringService.grpc.pb.h" |
12 | #include "MonitoringService.pb.h" |
13 | #include "Service.grpc.pb.h" |
14 | #include "Service.pb.h" |
15 | #include "index/Index.h" |
16 | #include "index/Serialization.h" |
17 | #include "index/Symbol.h" |
18 | #include "index/remote/marshalling/Marshalling.h" |
19 | #include "support/Context.h" |
20 | #include "support/Logger.h" |
21 | #include "support/Shutdown.h" |
22 | #include "support/ThreadsafeFS.h" |
23 | #include "support/Trace.h" |
24 | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
25 | #include "llvm/ADT/StringRef.h" |
26 | #include "llvm/Support/Chrono.h" |
27 | #include "llvm/Support/CommandLine.h" |
28 | #include "llvm/Support/Error.h" |
29 | #include "llvm/Support/FileSystem.h" |
30 | #include "llvm/Support/FormatVariadic.h" |
31 | #include "llvm/Support/Path.h" |
32 | #include "llvm/Support/Signals.h" |
33 | #include "llvm/Support/VirtualFileSystem.h" |
34 | |
35 | #include <chrono> |
36 | #include <grpc++/grpc++.h> |
37 | #include <grpc++/health_check_service_interface.h> |
38 | #include <memory> |
39 | #include <optional> |
40 | #include <string> |
41 | #include <thread> |
42 | #include <utility> |
43 | |
44 | #if ENABLE_GRPC_REFLECTION |
45 | #include <grpc++/ext/proto_server_reflection_plugin.h> |
46 | #endif |
47 | |
48 | #ifdef __GLIBC__ |
49 | #include <malloc.h> |
50 | #endif |
51 | |
52 | namespace clang { |
53 | namespace clangd { |
54 | namespace remote { |
55 | namespace { |
56 | |
57 | static constexpr char Overview[] = R"( |
58 | This is an experimental remote index implementation. The server opens Dex and |
59 | awaits gRPC lookup requests from the client. |
60 | )" ; |
61 | |
62 | llvm::cl::opt<std::string> IndexPath(llvm::cl::desc("<INDEX FILE>" ), |
63 | llvm::cl::Positional, llvm::cl::Required); |
64 | |
65 | llvm::cl::opt<std::string> IndexRoot(llvm::cl::desc("<PROJECT ROOT>" ), |
66 | llvm::cl::Positional, llvm::cl::Required); |
67 | |
68 | llvm::cl::opt<Logger::Level> LogLevel{ |
69 | "log" , |
70 | llvm::cl::desc("Verbosity of log messages written to stderr" ), |
71 | values(clEnumValN(Logger::Error, "error" , "Error messages only" ), |
72 | clEnumValN(Logger::Info, "info" , "High level execution tracing" ), |
73 | clEnumValN(Logger::Debug, "verbose" , "Low level details" )), |
74 | llvm::cl::init(Val: Logger::Info), |
75 | }; |
76 | |
77 | llvm::cl::opt<bool> LogPublic{ |
78 | "log-public" , |
79 | llvm::cl::desc("Avoid logging potentially-sensitive request details" ), |
80 | llvm::cl::init(Val: false), |
81 | }; |
82 | |
83 | llvm::cl::opt<std::string> LogPrefix{ |
84 | "log-prefix" , |
85 | llvm::cl::desc("A string that'll be prepended to all log statements. " |
86 | "Useful when running multiple instances on same host." ), |
87 | }; |
88 | |
89 | llvm::cl::opt<std::string> TraceFile( |
90 | "trace-file" , |
91 | llvm::cl::desc("Path to the file where tracer logs will be stored" )); |
92 | |
93 | llvm::cl::opt<bool> PrettyPrint{ |
94 | "pretty" , |
95 | llvm::cl::desc("Pretty-print JSON output in the trace" ), |
96 | llvm::cl::init(Val: false), |
97 | }; |
98 | |
99 | llvm::cl::opt<std::string> ServerAddress( |
100 | "server-address" , llvm::cl::init(Val: "0.0.0.0:50051" ), |
101 | llvm::cl::desc("Address of the invoked server. Defaults to 0.0.0.0:50051" )); |
102 | |
103 | llvm::cl::opt<size_t> IdleTimeoutSeconds( |
104 | "idle-timeout" , llvm::cl::init(Val: 8 * 60), |
105 | llvm::cl::desc("Maximum time a channel may stay idle until server closes " |
106 | "the connection, in seconds. Defaults to 480." )); |
107 | |
108 | llvm::cl::opt<size_t> LimitResults( |
109 | "limit-results" , llvm::cl::init(Val: 10000), |
110 | llvm::cl::desc("Maximum number of results to stream as a response to " |
111 | "single request. Limit is to keep the server from being " |
112 | "DOS'd. Defaults to 10000." )); |
113 | |
114 | static Key<grpc::ServerContext *> CurrentRequest; |
115 | |
116 | class RemoteIndexServer final : public v1::SymbolIndex::Service { |
117 | public: |
118 | RemoteIndexServer(clangd::SymbolIndex &Index, llvm::StringRef IndexRoot) |
119 | : Index(Index) { |
120 | llvm::SmallString<256> NativePath = IndexRoot; |
121 | llvm::sys::path::native(path&: NativePath); |
122 | ProtobufMarshaller = std::unique_ptr<Marshaller>(new Marshaller( |
123 | /*RemoteIndexRoot=*/llvm::StringRef(NativePath), |
124 | /*LocalIndexRoot=*/"" )); |
125 | } |
126 | |
127 | private: |
128 | using stopwatch = std::chrono::steady_clock; |
129 | |
130 | grpc::Status Lookup(grpc::ServerContext *Context, |
131 | const LookupRequest *Request, |
132 | grpc::ServerWriter<LookupReply> *Reply) override { |
133 | auto StartTime = stopwatch::now(); |
134 | WithContextValue WithRequestContext(CurrentRequest, Context); |
135 | logRequest(*Request); |
136 | trace::Span Tracer("LookupRequest" ); |
137 | auto Req = ProtobufMarshaller->fromProtobuf(Message: Request); |
138 | if (!Req) { |
139 | elog(Fmt: "Can not parse LookupRequest from protobuf: {0}" , Vals: Req.takeError()); |
140 | return grpc::Status::CANCELLED; |
141 | } |
142 | unsigned Sent = 0; |
143 | unsigned FailedToSend = 0; |
144 | bool HasMore = false; |
145 | Index.lookup(Req: *Req, Callback: [&](const clangd::Symbol &Item) { |
146 | if (Sent >= LimitResults) { |
147 | HasMore = true; |
148 | return; |
149 | } |
150 | auto SerializedItem = ProtobufMarshaller->toProtobuf(From: Item); |
151 | if (!SerializedItem) { |
152 | elog(Fmt: "Unable to convert Symbol to protobuf: {0}" , |
153 | Vals: SerializedItem.takeError()); |
154 | ++FailedToSend; |
155 | return; |
156 | } |
157 | LookupReply NextMessage; |
158 | *NextMessage.mutable_stream_result() = *SerializedItem; |
159 | logResponse(NextMessage); |
160 | Reply->Write(NextMessage); |
161 | ++Sent; |
162 | }); |
163 | if (HasMore) |
164 | log(Fmt: "[public] Limiting result size for Lookup request." ); |
165 | LookupReply LastMessage; |
166 | LastMessage.mutable_final_result()->set_has_more(HasMore); |
167 | logResponse(LastMessage); |
168 | Reply->Write(LastMessage); |
169 | SPAN_ATTACH(Tracer, "Sent" , Sent); |
170 | SPAN_ATTACH(Tracer, "Failed to send" , FailedToSend); |
171 | logRequestSummary(RequestName: "v1/Lookup" , Sent, StartTime); |
172 | return grpc::Status::OK; |
173 | } |
174 | |
175 | grpc::Status FuzzyFind(grpc::ServerContext *Context, |
176 | const FuzzyFindRequest *Request, |
177 | grpc::ServerWriter<FuzzyFindReply> *Reply) override { |
178 | auto StartTime = stopwatch::now(); |
179 | WithContextValue WithRequestContext(CurrentRequest, Context); |
180 | logRequest(*Request); |
181 | trace::Span Tracer("FuzzyFindRequest" ); |
182 | auto Req = ProtobufMarshaller->fromProtobuf(Message: Request); |
183 | if (!Req) { |
184 | elog(Fmt: "Can not parse FuzzyFindRequest from protobuf: {0}" , |
185 | Vals: Req.takeError()); |
186 | return grpc::Status::CANCELLED; |
187 | } |
188 | if (!Req->Limit || *Req->Limit > LimitResults) { |
189 | log(Fmt: "[public] Limiting result size for FuzzyFind request from {0} to {1}" , |
190 | Vals&: Req->Limit, Vals&: LimitResults); |
191 | Req->Limit = LimitResults; |
192 | } |
193 | unsigned Sent = 0; |
194 | unsigned FailedToSend = 0; |
195 | bool HasMore = Index.fuzzyFind(Req: *Req, Callback: [&](const clangd::Symbol &Item) { |
196 | auto SerializedItem = ProtobufMarshaller->toProtobuf(From: Item); |
197 | if (!SerializedItem) { |
198 | elog(Fmt: "Unable to convert Symbol to protobuf: {0}" , |
199 | Vals: SerializedItem.takeError()); |
200 | ++FailedToSend; |
201 | return; |
202 | } |
203 | FuzzyFindReply NextMessage; |
204 | *NextMessage.mutable_stream_result() = *SerializedItem; |
205 | logResponse(NextMessage); |
206 | Reply->Write(NextMessage); |
207 | ++Sent; |
208 | }); |
209 | FuzzyFindReply LastMessage; |
210 | LastMessage.mutable_final_result()->set_has_more(HasMore); |
211 | logResponse(LastMessage); |
212 | Reply->Write(LastMessage); |
213 | SPAN_ATTACH(Tracer, "Sent" , Sent); |
214 | SPAN_ATTACH(Tracer, "Failed to send" , FailedToSend); |
215 | logRequestSummary(RequestName: "v1/FuzzyFind" , Sent, StartTime); |
216 | return grpc::Status::OK; |
217 | } |
218 | |
219 | grpc::Status Refs(grpc::ServerContext *Context, const RefsRequest *Request, |
220 | grpc::ServerWriter<RefsReply> *Reply) override { |
221 | auto StartTime = stopwatch::now(); |
222 | WithContextValue WithRequestContext(CurrentRequest, Context); |
223 | logRequest(*Request); |
224 | trace::Span Tracer("RefsRequest" ); |
225 | auto Req = ProtobufMarshaller->fromProtobuf(Message: Request); |
226 | if (!Req) { |
227 | elog(Fmt: "Can not parse RefsRequest from protobuf: {0}" , Vals: Req.takeError()); |
228 | return grpc::Status::CANCELLED; |
229 | } |
230 | if (!Req->Limit || *Req->Limit > LimitResults) { |
231 | log(Fmt: "[public] Limiting result size for Refs request from {0} to {1}." , |
232 | Vals&: Req->Limit, Vals&: LimitResults); |
233 | Req->Limit = LimitResults; |
234 | } |
235 | unsigned Sent = 0; |
236 | unsigned FailedToSend = 0; |
237 | bool HasMore = Index.refs(Req: *Req, Callback: [&](const clangd::Ref &Item) { |
238 | auto SerializedItem = ProtobufMarshaller->toProtobuf(From: Item); |
239 | if (!SerializedItem) { |
240 | elog(Fmt: "Unable to convert Ref to protobuf: {0}" , |
241 | Vals: SerializedItem.takeError()); |
242 | ++FailedToSend; |
243 | return; |
244 | } |
245 | RefsReply NextMessage; |
246 | *NextMessage.mutable_stream_result() = *SerializedItem; |
247 | logResponse(NextMessage); |
248 | Reply->Write(NextMessage); |
249 | ++Sent; |
250 | }); |
251 | RefsReply LastMessage; |
252 | LastMessage.mutable_final_result()->set_has_more(HasMore); |
253 | logResponse(LastMessage); |
254 | Reply->Write(LastMessage); |
255 | SPAN_ATTACH(Tracer, "Sent" , Sent); |
256 | SPAN_ATTACH(Tracer, "Failed to send" , FailedToSend); |
257 | logRequestSummary(RequestName: "v1/Refs" , Sent, StartTime); |
258 | return grpc::Status::OK; |
259 | } |
260 | |
261 | grpc::Status |
262 | ContainedRefs(grpc::ServerContext *Context, |
263 | const ContainedRefsRequest *Request, |
264 | grpc::ServerWriter<ContainedRefsReply> *Reply) override { |
265 | auto StartTime = stopwatch::now(); |
266 | WithContextValue WithRequestContext(CurrentRequest, Context); |
267 | logRequest(*Request); |
268 | trace::Span Tracer("ContainedRefsRequest" ); |
269 | auto Req = ProtobufMarshaller->fromProtobuf(Message: Request); |
270 | if (!Req) { |
271 | elog(Fmt: "Can not parse ContainedRefsRequest from protobuf: {0}" , |
272 | Vals: Req.takeError()); |
273 | return grpc::Status::CANCELLED; |
274 | } |
275 | if (!Req->Limit || *Req->Limit > LimitResults) { |
276 | log(Fmt: "[public] Limiting result size for ContainedRefs request from {0} to " |
277 | "{1}." , |
278 | Vals&: Req->Limit, Vals&: LimitResults); |
279 | Req->Limit = LimitResults; |
280 | } |
281 | unsigned Sent = 0; |
282 | unsigned FailedToSend = 0; |
283 | bool HasMore = |
284 | Index.containedRefs(Req: *Req, Callback: [&](const clangd::ContainedRefsResult &Item) { |
285 | auto SerializedItem = ProtobufMarshaller->toProtobuf(Item); |
286 | if (!SerializedItem) { |
287 | elog("Unable to convert ContainedRefsResult to protobuf: {0}" , |
288 | SerializedItem.takeError()); |
289 | ++FailedToSend; |
290 | return; |
291 | } |
292 | ContainedRefsReply NextMessage; |
293 | *NextMessage.mutable_stream_result() = *SerializedItem; |
294 | logResponse(NextMessage); |
295 | Reply->Write(NextMessage); |
296 | ++Sent; |
297 | }); |
298 | ContainedRefsReply LastMessage; |
299 | LastMessage.mutable_final_result()->set_has_more(HasMore); |
300 | logResponse(LastMessage); |
301 | Reply->Write(LastMessage); |
302 | SPAN_ATTACH(Tracer, "Sent" , Sent); |
303 | SPAN_ATTACH(Tracer, "Failed to send" , FailedToSend); |
304 | logRequestSummary(RequestName: "v1/ContainedRefs" , Sent, StartTime); |
305 | return grpc::Status::OK; |
306 | } |
307 | |
308 | grpc::Status Relations(grpc::ServerContext *Context, |
309 | const RelationsRequest *Request, |
310 | grpc::ServerWriter<RelationsReply> *Reply) override { |
311 | auto StartTime = stopwatch::now(); |
312 | WithContextValue WithRequestContext(CurrentRequest, Context); |
313 | logRequest(*Request); |
314 | trace::Span Tracer("RelationsRequest" ); |
315 | auto Req = ProtobufMarshaller->fromProtobuf(Message: Request); |
316 | if (!Req) { |
317 | elog(Fmt: "Can not parse RelationsRequest from protobuf: {0}" , |
318 | Vals: Req.takeError()); |
319 | return grpc::Status::CANCELLED; |
320 | } |
321 | if (!Req->Limit || *Req->Limit > LimitResults) { |
322 | log(Fmt: "[public] Limiting result size for Relations request from {0} to " |
323 | "{1}." , |
324 | Vals&: Req->Limit, Vals&: LimitResults); |
325 | Req->Limit = LimitResults; |
326 | } |
327 | unsigned Sent = 0; |
328 | unsigned FailedToSend = 0; |
329 | Index.relations( |
330 | Req: *Req, Callback: [&](const SymbolID &Subject, const clangd::Symbol &Object) { |
331 | auto SerializedItem = ProtobufMarshaller->toProtobuf(Subject, Object); |
332 | if (!SerializedItem) { |
333 | elog(Fmt: "Unable to convert Relation to protobuf: {0}" , |
334 | Vals: SerializedItem.takeError()); |
335 | ++FailedToSend; |
336 | return; |
337 | } |
338 | RelationsReply NextMessage; |
339 | *NextMessage.mutable_stream_result() = *SerializedItem; |
340 | logResponse(NextMessage); |
341 | Reply->Write(NextMessage); |
342 | ++Sent; |
343 | }); |
344 | RelationsReply LastMessage; |
345 | LastMessage.mutable_final_result()->set_has_more(true); |
346 | logResponse(LastMessage); |
347 | Reply->Write(LastMessage); |
348 | SPAN_ATTACH(Tracer, "Sent" , Sent); |
349 | SPAN_ATTACH(Tracer, "Failed to send" , FailedToSend); |
350 | logRequestSummary(RequestName: "v1/Relations" , Sent, StartTime); |
351 | return grpc::Status::OK; |
352 | } |
353 | |
354 | // Proxy object to allow proto messages to be lazily serialized as text. |
355 | struct TextProto { |
356 | const google::protobuf::Message &M; |
357 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, |
358 | const TextProto &P) { |
359 | return OS << P.M.DebugString(); |
360 | } |
361 | }; |
362 | |
363 | void logRequest(const google::protobuf::Message &M) { |
364 | vlog("<<< {0}\n{1}" , M.GetDescriptor()->name(), TextProto{M}); |
365 | } |
366 | void logResponse(const google::protobuf::Message &M) { |
367 | vlog(">>> {0}\n{1}" , M.GetDescriptor()->name(), TextProto{M}); |
368 | } |
369 | void logRequestSummary(llvm::StringLiteral RequestName, unsigned Sent, |
370 | stopwatch::time_point StartTime) { |
371 | auto Duration = stopwatch::now() - StartTime; |
372 | auto Millis = |
373 | std::chrono::duration_cast<std::chrono::milliseconds>(d: Duration).count(); |
374 | log(Fmt: "[public] request {0} => OK: {1} results in {2}ms" , Vals&: RequestName, Vals&: Sent, |
375 | Vals&: Millis); |
376 | } |
377 | |
378 | std::unique_ptr<Marshaller> ProtobufMarshaller; |
379 | clangd::SymbolIndex &Index; |
380 | }; |
381 | |
382 | class Monitor final : public v1::Monitor::Service { |
383 | public: |
384 | Monitor(llvm::sys::TimePoint<> IndexAge) |
385 | : StartTime(std::chrono::system_clock::now()), IndexBuildTime(IndexAge) {} |
386 | |
387 | void updateIndex(llvm::sys::TimePoint<> UpdateTime) { |
388 | IndexBuildTime.exchange(i: UpdateTime); |
389 | } |
390 | |
391 | private: |
392 | // FIXME(kirillbobyrev): Most fields should be populated when the index |
393 | // reloads (probably in adjacent metadata.txt file next to loaded .idx) but |
394 | // they aren't right now. |
395 | grpc::Status MonitoringInfo(grpc::ServerContext *Context, |
396 | const v1::MonitoringInfoRequest *Request, |
397 | v1::MonitoringInfoReply *Reply) override { |
398 | Reply->set_uptime_seconds(std::chrono::duration_cast<std::chrono::seconds>( |
399 | d: std::chrono::system_clock::now() - StartTime) |
400 | .count()); |
401 | // FIXME(kirillbobyrev): We are currently making use of the last |
402 | // modification time of the index artifact to deduce its age. This is wrong |
403 | // as it doesn't account for the indexing delay. Propagate some metadata |
404 | // with the index artifacts to indicate time of the commit we indexed. |
405 | Reply->set_index_age_seconds( |
406 | std::chrono::duration_cast<std::chrono::seconds>( |
407 | d: std::chrono::system_clock::now() - IndexBuildTime.load()) |
408 | .count()); |
409 | return grpc::Status::OK; |
410 | } |
411 | |
412 | const llvm::sys::TimePoint<> StartTime; |
413 | std::atomic<llvm::sys::TimePoint<>> IndexBuildTime; |
414 | }; |
415 | |
416 | void maybeTrimMemory() { |
417 | #if defined(__GLIBC__) && CLANGD_MALLOC_TRIM |
418 | malloc_trim(pad: 0); |
419 | #endif |
420 | } |
421 | |
422 | // Detect changes in \p IndexPath file and load new versions of the index |
423 | // whenever they become available. |
424 | void hotReload(clangd::SwapIndex &Index, llvm::StringRef IndexPath, |
425 | llvm::vfs::Status &LastStatus, |
426 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> &FS, |
427 | Monitor &Monitor) { |
428 | // glibc malloc doesn't shrink an arena if there are items living at the end, |
429 | // which might happen since we destroy the old index after building new one. |
430 | // Trim more aggresively to keep memory usage of the server low. |
431 | // Note that we do it deliberately here rather than after Index.reset(), |
432 | // because old index might still be kept alive after the reset call if we are |
433 | // serving requests. |
434 | maybeTrimMemory(); |
435 | auto Status = FS->status(Path: IndexPath); |
436 | // Requested file is same as loaded index: no reload is needed. |
437 | if (!Status || (Status->getLastModificationTime() == |
438 | LastStatus.getLastModificationTime() && |
439 | Status->getSize() == LastStatus.getSize())) |
440 | return; |
441 | vlog(Fmt: "Found different index version: existing index was modified at " |
442 | "{0}, new index was modified at {1}. Attempting to reload." , |
443 | Vals: LastStatus.getLastModificationTime(), Vals: Status->getLastModificationTime()); |
444 | LastStatus = *Status; |
445 | std::unique_ptr<clang::clangd::SymbolIndex> NewIndex = |
446 | loadIndex(Filename: IndexPath, Origin: SymbolOrigin::Static, /*UseDex=*/true, |
447 | /*SupportContainedRefs=*/true); |
448 | if (!NewIndex) { |
449 | elog(Fmt: "Failed to load new index. Old index will be served." ); |
450 | return; |
451 | } |
452 | Index.reset(std::move(NewIndex)); |
453 | Monitor.updateIndex(UpdateTime: Status->getLastModificationTime()); |
454 | log(Fmt: "New index version loaded. Last modification time: {0}, size: {1} bytes." , |
455 | Vals: Status->getLastModificationTime(), Vals: Status->getSize()); |
456 | } |
457 | |
458 | void runServerAndWait(clangd::SymbolIndex &Index, llvm::StringRef ServerAddress, |
459 | llvm::StringRef IndexPath, Monitor &Monitor) { |
460 | RemoteIndexServer Service(Index, IndexRoot); |
461 | |
462 | grpc::EnableDefaultHealthCheckService(enable: true); |
463 | #if ENABLE_GRPC_REFLECTION |
464 | grpc::reflection::InitProtoReflectionServerBuilderPlugin(); |
465 | #endif |
466 | grpc::ServerBuilder Builder; |
467 | Builder.AddListeningPort(addr_uri: ServerAddress.str(), |
468 | creds: grpc::InsecureServerCredentials()); |
469 | Builder.AddChannelArgument(GRPC_ARG_MAX_CONNECTION_IDLE_MS, |
470 | value: IdleTimeoutSeconds * 1000); |
471 | Builder.RegisterService(&Service); |
472 | Builder.RegisterService(&Monitor); |
473 | std::unique_ptr<grpc::Server> Server(Builder.BuildAndStart()); |
474 | log(Fmt: "Server listening on {0}" , Vals&: ServerAddress); |
475 | |
476 | std::thread ServerShutdownWatcher([&]() { |
477 | static constexpr auto WatcherFrequency = std::chrono::seconds(5); |
478 | while (!clang::clangd::shutdownRequested()) |
479 | std::this_thread::sleep_for(rtime: WatcherFrequency); |
480 | Server->Shutdown(); |
481 | }); |
482 | |
483 | Server->Wait(); |
484 | ServerShutdownWatcher.join(); |
485 | } |
486 | |
487 | std::unique_ptr<Logger> makeLogger(llvm::StringRef LogPrefix, |
488 | llvm::raw_ostream &OS) { |
489 | std::unique_ptr<Logger> Base; |
490 | if (LogPublic) { |
491 | // Redacted mode: |
492 | // - messages outside the scope of a request: log fully |
493 | // - messages tagged [public]: log fully |
494 | // - errors: log the format string |
495 | // - others: drop |
496 | class RedactedLogger : public StreamLogger { |
497 | public: |
498 | using StreamLogger::StreamLogger; |
499 | void log(Level L, const char *Fmt, |
500 | const llvm::formatv_object_base &Message) override { |
501 | if (Context::current().get(Key: CurrentRequest) == nullptr || |
502 | llvm::StringRef(Fmt).starts_with(Prefix: "[public]" )) |
503 | return StreamLogger::log(L, Fmt, Message); |
504 | if (L >= Error) |
505 | return StreamLogger::log(L, Fmt, |
506 | Message: llvm::formatv(Fmt: "[redacted] {0}" , Vals&: Fmt)); |
507 | } |
508 | }; |
509 | Base = std::make_unique<RedactedLogger>(args&: OS, args&: LogLevel); |
510 | } else { |
511 | Base = std::make_unique<StreamLogger>(args&: OS, args&: LogLevel); |
512 | } |
513 | |
514 | if (LogPrefix.empty()) |
515 | return Base; |
516 | class PrefixedLogger : public Logger { |
517 | std::string LogPrefix; |
518 | std::unique_ptr<Logger> Base; |
519 | |
520 | public: |
521 | PrefixedLogger(llvm::StringRef LogPrefix, std::unique_ptr<Logger> Base) |
522 | : LogPrefix(LogPrefix.str()), Base(std::move(Base)) {} |
523 | void log(Level L, const char *Fmt, |
524 | const llvm::formatv_object_base &Message) override { |
525 | Base->log(L, Fmt, Message: llvm::formatv(Fmt: "[{0}] {1}" , Vals&: LogPrefix, Vals: Message)); |
526 | } |
527 | }; |
528 | return std::make_unique<PrefixedLogger>(args&: LogPrefix, args: std::move(Base)); |
529 | } |
530 | |
531 | } // namespace |
532 | } // namespace remote |
533 | } // namespace clangd |
534 | } // namespace clang |
535 | |
536 | using clang::clangd::elog; |
537 | |
538 | int main(int argc, char *argv[]) { |
539 | using namespace clang::clangd::remote; |
540 | llvm::cl::ParseCommandLineOptions(argc, argv, Overview); |
541 | llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
542 | llvm::sys::SetInterruptFunction(&clang::clangd::requestShutdown); |
543 | |
544 | if (!llvm::sys::path::is_absolute(path: IndexRoot)) { |
545 | llvm::errs() << "Index root should be an absolute path.\n" ; |
546 | return -1; |
547 | } |
548 | |
549 | llvm::errs().SetBuffered(); |
550 | auto Logger = makeLogger(LogPrefix: LogPrefix.getValue(), OS&: llvm::errs()); |
551 | clang::clangd::LoggingSession LoggingSession(*Logger); |
552 | |
553 | std::optional<llvm::raw_fd_ostream> TracerStream; |
554 | std::unique_ptr<clang::clangd::trace::EventTracer> Tracer; |
555 | if (!TraceFile.empty()) { |
556 | std::error_code EC; |
557 | TracerStream.emplace(args&: TraceFile, args&: EC, |
558 | args: llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write); |
559 | if (EC) { |
560 | TracerStream.reset(); |
561 | elog(Fmt: "Error while opening trace file {0}: {1}" , Vals&: TraceFile, Vals: EC.message()); |
562 | } else { |
563 | // FIXME(kirillbobyrev): Also create metrics tracer to track latency and |
564 | // accumulate other request statistics. |
565 | Tracer = clang::clangd::trace::createJSONTracer(OS&: *TracerStream, |
566 | /*PrettyPrint=*/Pretty: false); |
567 | clang::clangd::vlog(Fmt: "Successfully created a tracer." ); |
568 | } |
569 | } |
570 | |
571 | std::optional<clang::clangd::trace::Session> TracingSession; |
572 | if (Tracer) |
573 | TracingSession.emplace(args&: *Tracer); |
574 | |
575 | clang::clangd::RealThreadsafeFS TFS; |
576 | auto FS = TFS.view(CWD: std::nullopt); |
577 | auto Status = FS->status(Path: IndexPath); |
578 | if (!Status) { |
579 | elog(Fmt: "{0} does not exist." , Vals&: IndexPath); |
580 | return Status.getError().value(); |
581 | } |
582 | |
583 | auto SymIndex = clang::clangd::loadIndex( |
584 | Filename: IndexPath, Origin: clang::clangd::SymbolOrigin::Static, /*UseDex=*/true, |
585 | /*SupportContainedRefs=*/true); |
586 | if (!SymIndex) { |
587 | llvm::errs() << "Failed to open the index.\n" ; |
588 | return -1; |
589 | } |
590 | clang::clangd::SwapIndex Index(std::move(SymIndex)); |
591 | |
592 | Monitor Monitor(Status->getLastModificationTime()); |
593 | |
594 | std::thread HotReloadThread([&Index, &Status, &FS, &Monitor]() { |
595 | llvm::vfs::Status LastStatus = *Status; |
596 | static constexpr auto RefreshFrequency = std::chrono::seconds(30); |
597 | while (!clang::clangd::shutdownRequested()) { |
598 | hotReload(Index, IndexPath: llvm::StringRef(IndexPath), LastStatus, FS, Monitor); |
599 | std::this_thread::sleep_for(rtime: RefreshFrequency); |
600 | } |
601 | }); |
602 | |
603 | runServerAndWait(Index, ServerAddress, IndexPath, Monitor); |
604 | |
605 | HotReloadThread.join(); |
606 | } |
607 | |