diff --git a/clang-tools-extra/clangd/index/remote/CMakeLists.txt b/clang-tools-extra/clangd/index/remote/CMakeLists.txt --- a/clang-tools-extra/clangd/index/remote/CMakeLists.txt +++ b/clang-tools-extra/clangd/index/remote/CMakeLists.txt @@ -38,6 +38,7 @@ add_subdirectory(marshalling) add_subdirectory(server) + add_subdirectory(monitor) else() # Provides a no-op implementation of clangdRemoteIndex. add_subdirectory(unimplemented) diff --git a/clang-tools-extra/clangd/index/remote/monitor/CMakeLists.txt b/clang-tools-extra/clangd/index/remote/monitor/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/index/remote/monitor/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS + Support + ) +add_clang_executable(clangd-index-server-monitor + Monitor.cpp + + DEPENDS + RemoteIndexServiceProto + ) + +target_link_libraries(clangd-index-server-monitor + PRIVATE + clangBasic + clangdSupport + + MonitoringServiceProto + RemoteIndexServiceProto + ) diff --git a/clang-tools-extra/clangd/index/remote/monitor/Monitor.cpp b/clang-tools-extra/clangd/index/remote/monitor/Monitor.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/index/remote/monitor/Monitor.cpp @@ -0,0 +1,76 @@ +//===--- Monitor.cpp - Request server monitoring information through CLI --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "MonitoringService.grpc.pb.h" +#include "MonitoringService.pb.h" + +#include "support/Logger.h" +#include "clang/Basic/Version.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Signals.h" + +#include +#include +#include + +namespace clang { +namespace clangd { +namespace remote { +namespace { + +static constexpr char Overview[] = R"( +This tool requests monitoring information (uptime, index freshness) from the +server and prints it to stdout. +)"; + +llvm::cl::opt + ServerAddress("server-address", llvm::cl::Positional, + llvm::cl::desc("Address of the invoked server."), + llvm::cl::Required); + +} // namespace +} // namespace remote +} // namespace clangd +} // namespace clang + +int main(int argc, char *argv[]) { + using namespace clang::clangd::remote; + llvm::cl::ParseCommandLineOptions(argc, argv, Overview); + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + const auto Channel = + grpc::CreateChannel(ServerAddress, grpc::InsecureChannelCredentials()); + const auto Stub = clang::clangd::remote::v1::Monitor::NewStub(Channel); + grpc::ClientContext Context; + Context.set_deadline(std::chrono::system_clock::now() + + std::chrono::seconds(10)); + Context.AddMetadata("version", clang::getClangToolFullVersion("clangd")); + const clang::clangd::remote::v1::MonitoringInfoRequest Request; + clang::clangd::remote::v1::MonitoringInfoReply Response; + const auto Status = Stub->MonitoringInfo(&Context, Request, &Response); + if (!Status.ok()) { + clang::clangd::elog("Can not request monitoring information ({0}): {1}\n", + Status.error_code(), Status.error_message()); + return -1; + } + std::string Output; + google::protobuf::util::JsonPrintOptions Options; + Options.add_whitespace = true; + Options.always_print_primitive_fields = true; + Options.preserve_proto_field_names = true; + const auto JsonStatus = + google::protobuf::util::MessageToJsonString(Response, &Output, Options); + if (!JsonStatus.ok()) { + clang::clangd::elog("Can not convert response ({0}) to JSON ({1}): {2}\n", + Response.DebugString(), JsonStatus.error_code(), + JsonStatus.error_message()); + return -1; + } + llvm::outs() << Output; +} diff --git a/clang-tools-extra/clangd/test/CMakeLists.txt b/clang-tools-extra/clangd/test/CMakeLists.txt --- a/clang-tools-extra/clangd/test/CMakeLists.txt +++ b/clang-tools-extra/clangd/test/CMakeLists.txt @@ -22,7 +22,7 @@ endif() if(CLANGD_ENABLE_REMOTE) - list(APPEND CLANGD_TEST_DEPS clangd-index-server) + list(APPEND CLANGD_TEST_DEPS clangd-index-server clangd-index-server-monitor) endif() foreach(dep FileCheck count not llvm-config) diff --git a/clang-tools-extra/clangd/test/remote-index/pipeline_helper.py b/clang-tools-extra/clangd/test/remote-index/pipeline_helper.py --- a/clang-tools-extra/clangd/test/remote-index/pipeline_helper.py +++ b/clang-tools-extra/clangd/test/remote-index/pipeline_helper.py @@ -15,9 +15,10 @@ import sys import time import threading +import json -def kill_server_after_delay(server_process): +def kill_process_after_delay(server_process): time.sleep(10) if server_process.poll() is None: server_process.kill() @@ -48,7 +49,7 @@ # This will kill index_server_process if it hangs without printing init # message. shutdown_thread = threading.Thread( - target=kill_server_after_delay, args=(index_server_process,)) + target=kill_process_after_delay, args=(index_server_process,)) shutdown_thread.daemon = True shutdown_thread.start() @@ -67,6 +68,16 @@ print('Server initialization failed. Shutting down.', file=sys.stderr) sys.exit(1) + print('Running clangd-index-server-monitor...', file=sys.stderr) + index_server_monitor_process = subprocess.Popen([ + 'clangd-index-server-monitor', server_address, + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + index_server_monitor_process.wait() + for line in index_server_monitor_process.stderr: + args.server_log.write(line) + args.server_log.flush() + in_file = open(args.input_file_name) print('Staring clangd...', file=sys.stderr) @@ -84,6 +95,17 @@ args.server_log.write(line) args.server_log.flush() + monitor_info = index_server_monitor_process.stdout.read() + if not monitor_info: + print('clangd-index-server-monitor\'s output is empty', file=sys.stderr) + sys.exit(1) + monitor_info = json.loads(monitor_info) + for key in ['uptime_seconds', 'index_age_seconds', 'index_commit_hash', + 'index_link']: + if not key in monitor_info: + print('Key', key, 'not found in clangd-index-server-monitor output', + file=sys.stderr) + sys.exit(1) if __name__ == '__main__': main()