ClickHouse
673 строки · 24.1 Кб
1#include "Keeper.h"
2
3#include <Common/ClickHouseRevision.h>
4#include <Common/getMultipleKeysFromConfig.h>
5#include <Common/DNSResolver.h>
6#include <Interpreters/DNSCacheUpdater.h>
7#include <Coordination/Defines.h>
8#include <Common/Config/ConfigReloader.h>
9#include <filesystem>
10#include <IO/UseSSL.h>
11#include <Core/ServerUUID.h>
12#include <Common/logger_useful.h>
13#include <Common/CgroupsMemoryUsageObserver.h>
14#include <Common/ErrorHandlers.h>
15#include <Common/assertProcessUserMatchesDataOwner.h>
16#include <Common/makeSocketAddress.h>
17#include <Server/waitServersToFinish.h>
18#include <Server/CloudPlacementInfo.h>
19#include <base/getMemoryAmount.h>
20#include <base/scope_guard.h>
21#include <base/safeExit.h>
22#include <Poco/Net/NetException.h>
23#include <Poco/Net/TCPServerParams.h>
24#include <Poco/Net/TCPServer.h>
25#include <Poco/Util/HelpFormatter.h>
26#include <Poco/Environment.h>
27#include <sys/stat.h>
28#include <pwd.h>
29
30#include <Interpreters/Context.h>
31
32#include <Coordination/FourLetterCommand.h>
33#include <Coordination/KeeperAsynchronousMetrics.h>
34
35#include <Server/HTTP/HTTPServer.h>
36#include <Server/HTTPHandlerFactory.h>
37#include <Server/KeeperReadinessHandler.h>
38#include <Server/PrometheusMetricsWriter.h>
39#include <Server/TCPServer.h>
40
41#include "Core/Defines.h"
42#include "config.h"
43#include <Common/config_version.h>
44#include "config_tools.h"
45
46
47#if USE_SSL
48# include <Poco/Net/Context.h>
49# include <Poco/Net/SecureServerSocket.h>
50# include <Server/CertificateReloader.h>
51#endif
52
53#include <Server/ProtocolServerAdapter.h>
54#include <Server/KeeperTCPHandlerFactory.h>
55
56#include <Disks/registerDisks.h>
57
58#include <incbin.h>
59/// A minimal file used when the keeper is run without installation
60INCBIN(keeper_resource_embedded_xml, SOURCE_DIR "/programs/keeper/keeper_embedded.xml");
61
62int mainEntryClickHouseKeeper(int argc, char ** argv)
63{
64DB::Keeper app;
65
66try
67{
68return app.run(argc, argv);
69}
70catch (...)
71{
72std::cerr << DB::getCurrentExceptionMessage(true) << "\n";
73auto code = DB::getCurrentExceptionCode();
74return code ? code : 1;
75}
76}
77
78#ifdef CLICKHOUSE_KEEPER_STANDALONE_BUILD
79
80// Weak symbols don't work correctly on Darwin
81// so we have a stub implementation to avoid linker errors
82void collectCrashLog(
83Int32, UInt64, const String &, const StackTrace &)
84{}
85
86#endif
87
88namespace DB
89{
90
91namespace ErrorCodes
92{
93extern const int NO_ELEMENTS_IN_CONFIG;
94extern const int SUPPORT_IS_DISABLED;
95extern const int NETWORK_ERROR;
96extern const int LOGICAL_ERROR;
97}
98
99Poco::Net::SocketAddress Keeper::socketBindListen(Poco::Net::ServerSocket & socket, const std::string & host, UInt16 port, [[maybe_unused]] bool secure) const
100{
101auto address = makeSocketAddress(host, port, &logger());
102socket.bind(address, /* reuseAddress = */ true, /* reusePort = */ config().getBool("listen_reuse_port", false));
103socket.listen(/* backlog = */ config().getUInt("listen_backlog", 64));
104
105return address;
106}
107
108void Keeper::createServer(const std::string & listen_host, const char * port_name, bool listen_try, CreateServerFunc && func) const
109{
110/// For testing purposes, user may omit tcp_port or http_port or https_port in configuration file.
111if (!config().has(port_name))
112return;
113
114auto port = config().getInt(port_name);
115try
116{
117func(port);
118}
119catch (const Poco::Exception &)
120{
121if (listen_try)
122{
123LOG_WARNING(&logger(), "Listen [{}]:{} failed: {}. If it is an IPv6 or IPv4 address and your host has disabled IPv6 or IPv4, "
124"then consider to "
125"specify not disabled IPv4 or IPv6 address to listen in <listen_host> element of configuration "
126"file. Example for disabled IPv6: <listen_host>0.0.0.0</listen_host> ."
127" Example for disabled IPv4: <listen_host>::</listen_host>",
128listen_host, port, getCurrentExceptionMessage(false));
129}
130else
131{
132throw Exception(ErrorCodes::NETWORK_ERROR, "Listen [{}]:{} failed: {}", listen_host, port, getCurrentExceptionMessage(false));
133}
134}
135}
136
137void Keeper::uninitialize()
138{
139logger().information("shutting down");
140BaseDaemon::uninitialize();
141}
142
143int Keeper::run()
144{
145if (config().hasOption("help"))
146{
147Poco::Util::HelpFormatter help_formatter(Keeper::options());
148auto header_str = fmt::format("{0} [OPTION] [-- [ARG]...]\n"
149#if ENABLE_CLICKHOUSE_KEEPER_CLIENT
150"{0} client [OPTION]\n"
151#endif
152"positional arguments can be used to rewrite config.xml properties, for example, --http_port=8010",
153commandName());
154help_formatter.setHeader(header_str);
155help_formatter.format(std::cout);
156return 0;
157}
158if (config().hasOption("version"))
159{
160std::cout << VERSION_NAME << " keeper version " << VERSION_STRING << VERSION_OFFICIAL << "." << std::endl;
161return 0;
162}
163
164return Application::run(); // NOLINT
165}
166
167void Keeper::initialize(Poco::Util::Application & self)
168{
169ConfigProcessor::registerEmbeddedConfig("keeper_config.xml", std::string_view(reinterpret_cast<const char *>(gkeeper_resource_embedded_xmlData), gkeeper_resource_embedded_xmlSize));
170
171BaseDaemon::initialize(self);
172logger().information("starting up");
173
174LOG_INFO(&logger(), "OS Name = {}, OS Version = {}, OS Architecture = {}",
175Poco::Environment::osName(),
176Poco::Environment::osVersion(),
177Poco::Environment::osArchitecture());
178}
179
180std::string Keeper::getDefaultConfigFileName() const
181{
182return "keeper_config.xml";
183}
184
185void Keeper::handleCustomArguments(const std::string & arg, [[maybe_unused]] const std::string & value) // NOLINT
186{
187if (arg == "force-recovery")
188{
189assert(value.empty());
190config().setBool("keeper_server.force_recovery", true);
191return;
192}
193
194throw Exception(ErrorCodes::LOGICAL_ERROR, "Invalid argument {} provided", arg);
195}
196
197void Keeper::defineOptions(Poco::Util::OptionSet & options)
198{
199options.addOption(
200Poco::Util::Option("help", "h", "show help and exit")
201.required(false)
202.repeatable(false)
203.binding("help"));
204options.addOption(
205Poco::Util::Option("version", "V", "show version and exit")
206.required(false)
207.repeatable(false)
208.binding("version"));
209options.addOption(
210Poco::Util::Option("force-recovery", "force-recovery", "Force recovery mode allowing Keeper to overwrite cluster configuration without quorum")
211.required(false)
212.repeatable(false)
213.noArgument()
214.callback(Poco::Util::OptionCallback<Keeper>(this, &Keeper::handleCustomArguments)));
215BaseDaemon::defineOptions(options);
216}
217
218namespace
219{
220
221struct KeeperHTTPContext : public IHTTPContext
222{
223explicit KeeperHTTPContext(ContextPtr context_)
224: context(std::move(context_))
225{}
226
227uint64_t getMaxHstsAge() const override
228{
229return context->getConfigRef().getUInt64("keeper_server.hsts_max_age", 0);
230}
231
232uint64_t getMaxUriSize() const override
233{
234return context->getConfigRef().getUInt64("keeper_server.http_max_uri_size", 1048576);
235}
236
237uint64_t getMaxFields() const override
238{
239return context->getConfigRef().getUInt64("keeper_server.http_max_fields", 1000000);
240}
241
242uint64_t getMaxFieldNameSize() const override
243{
244return context->getConfigRef().getUInt64("keeper_server.http_max_field_name_size", 128 * 1024);
245}
246
247uint64_t getMaxFieldValueSize() const override
248{
249return context->getConfigRef().getUInt64("keeper_server.http_max_field_value_size", 128 * 1024);
250}
251
252uint64_t getMaxChunkSize() const override
253{
254return context->getConfigRef().getUInt64("keeper_server.http_max_chunk_size", 100_GiB);
255}
256
257Poco::Timespan getReceiveTimeout() const override
258{
259return {context->getConfigRef().getInt64("keeper_server.http_receive_timeout", DBMS_DEFAULT_RECEIVE_TIMEOUT_SEC), 0};
260}
261
262Poco::Timespan getSendTimeout() const override
263{
264return {context->getConfigRef().getInt64("keeper_server.http_send_timeout", DBMS_DEFAULT_SEND_TIMEOUT_SEC), 0};
265}
266
267ContextPtr context;
268};
269
270HTTPContextPtr httpContext()
271{
272return std::make_shared<KeeperHTTPContext>(Context::getGlobalContextInstance());
273}
274
275}
276
277int Keeper::main(const std::vector<std::string> & /*args*/)
278try
279{
280Poco::Logger * log = &logger();
281
282UseSSL use_ssl;
283
284MainThreadStatus::getInstance();
285
286#if !defined(NDEBUG) || !defined(__OPTIMIZE__)
287LOG_WARNING(log, "Keeper was built in debug mode. It will work slowly.");
288#endif
289
290#if defined(SANITIZER)
291LOG_WARNING(log, "Keeper was built with sanitizer. It will work slowly.");
292#endif
293
294if (!config().has("keeper_server"))
295throw Exception(ErrorCodes::NO_ELEMENTS_IN_CONFIG, "Keeper configuration (<keeper_server> section) not found in config");
296
297auto updateMemorySoftLimitInConfig = [&](Poco::Util::AbstractConfiguration & config)
298{
299UInt64 memory_soft_limit = 0;
300if (config.has("keeper_server.max_memory_usage_soft_limit"))
301{
302memory_soft_limit = config.getUInt64("keeper_server.max_memory_usage_soft_limit");
303}
304
305/// if memory soft limit is not set, we will use default value
306if (memory_soft_limit == 0)
307{
308Float64 ratio = 0.9;
309if (config.has("keeper_server.max_memory_usage_soft_limit_ratio"))
310ratio = config.getDouble("keeper_server.max_memory_usage_soft_limit_ratio");
311
312size_t physical_server_memory = getMemoryAmount();
313if (ratio > 0 && physical_server_memory > 0)
314{
315memory_soft_limit = static_cast<UInt64>(physical_server_memory * ratio);
316config.setUInt64("keeper_server.max_memory_usage_soft_limit", memory_soft_limit);
317}
318}
319LOG_INFO(log, "keeper_server.max_memory_usage_soft_limit is set to {}", formatReadableSizeWithBinarySuffix(memory_soft_limit));
320};
321
322updateMemorySoftLimitInConfig(config());
323
324std::string path;
325
326if (config().has("keeper_server.storage_path"))
327{
328path = config().getString("keeper_server.storage_path");
329}
330else if (config().has("keeper_server.log_storage_path"))
331{
332path = std::filesystem::path(config().getString("keeper_server.log_storage_path")).parent_path();
333}
334else if (config().has("keeper_server.snapshot_storage_path"))
335{
336path = std::filesystem::path(config().getString("keeper_server.snapshot_storage_path")).parent_path();
337}
338else if (std::filesystem::is_directory(std::filesystem::path{config().getString("path", DBMS_DEFAULT_PATH)} / "coordination"))
339{
340throw Exception(ErrorCodes::NO_ELEMENTS_IN_CONFIG,
341"By default 'keeper_server.storage_path' could be assigned to {}, but the directory {} already exists. Please specify 'keeper_server.storage_path' in the keeper configuration explicitly",
342KEEPER_DEFAULT_PATH, String{std::filesystem::path{config().getString("path", DBMS_DEFAULT_PATH)} / "coordination"});
343}
344else
345{
346path = KEEPER_DEFAULT_PATH;
347}
348
349std::filesystem::create_directories(path);
350
351/// Check that the process user id matches the owner of the data.
352assertProcessUserMatchesDataOwner(path, [&](const std::string & message){ LOG_WARNING(log, fmt::runtime(message)); });
353
354DB::ServerUUID::load(path + "/uuid", log);
355
356std::string include_from_path = config().getString("include_from", "/etc/metrika.xml");
357
358if (config().has(DB::PlacementInfo::PLACEMENT_CONFIG_PREFIX))
359{
360PlacementInfo::PlacementInfo::instance().initialize(config());
361}
362
363GlobalThreadPool::initialize(
364config().getUInt("max_thread_pool_size", 100),
365config().getUInt("max_thread_pool_free_size", 1000),
366config().getUInt("thread_pool_queue_size", 10000)
367);
368/// Wait for all threads to avoid possible use-after-free (for example logging objects can be already destroyed).
369SCOPE_EXIT({
370Stopwatch watch;
371LOG_INFO(log, "Waiting for background threads");
372GlobalThreadPool::instance().shutdown();
373LOG_INFO(log, "Background threads finished in {} ms", watch.elapsedMilliseconds());
374});
375
376static ServerErrorHandler error_handler;
377Poco::ErrorHandler::set(&error_handler);
378
379/// Initialize DateLUT early, to not interfere with running time of first query.
380LOG_DEBUG(log, "Initializing DateLUT.");
381DateLUT::serverTimezoneInstance();
382LOG_TRACE(log, "Initialized DateLUT with time zone '{}'.", DateLUT::serverTimezoneInstance().getTimeZone());
383
384/// Don't want to use DNS cache
385DNSResolver::instance().setDisableCacheFlag();
386
387Poco::ThreadPool server_pool(3, config().getUInt("max_connections", 1024));
388std::mutex servers_lock;
389auto servers = std::make_shared<std::vector<ProtocolServerAdapter>>();
390
391auto shared_context = Context::createShared();
392auto global_context = Context::createGlobal(shared_context.get());
393
394global_context->makeGlobalContext();
395global_context->setApplicationType(Context::ApplicationType::KEEPER);
396global_context->setPath(path);
397global_context->setRemoteHostFilter(config());
398
399if (config().has("macros"))
400global_context->setMacros(std::make_unique<Macros>(config(), "macros", log));
401
402registerDisks(/*global_skip_access_check=*/false);
403
404/// This object will periodically calculate some metrics.
405KeeperAsynchronousMetrics async_metrics(
406global_context,
407config().getUInt("asynchronous_metrics_update_period_s", 1),
408[&]() -> std::vector<ProtocolServerMetrics>
409{
410std::vector<ProtocolServerMetrics> metrics;
411
412std::lock_guard lock(servers_lock);
413metrics.reserve(servers->size());
414for (const auto & server : *servers)
415metrics.emplace_back(ProtocolServerMetrics{server.getPortName(), server.currentThreads()});
416return metrics;
417}
418);
419
420std::vector<std::string> listen_hosts = DB::getMultipleValuesFromConfig(config(), "", "listen_host");
421
422bool listen_try = config().getBool("listen_try", false);
423if (listen_hosts.empty())
424{
425listen_hosts.emplace_back("::1");
426listen_hosts.emplace_back("127.0.0.1");
427listen_try = true;
428}
429
430/// Initialize keeper RAFT. Do nothing if no keeper_server in config.
431global_context->initializeKeeperDispatcher(/* start_async = */ false);
432FourLetterCommandFactory::registerCommands(*global_context->getKeeperDispatcher());
433
434auto config_getter = [&] () -> const Poco::Util::AbstractConfiguration &
435{
436return global_context->getConfigRef();
437};
438
439auto tcp_receive_timeout = config().getInt64("keeper_server.socket_receive_timeout_sec", DBMS_DEFAULT_RECEIVE_TIMEOUT_SEC);
440auto tcp_send_timeout = config().getInt64("keeper_server.socket_send_timeout_sec", DBMS_DEFAULT_SEND_TIMEOUT_SEC);
441
442for (const auto & listen_host : listen_hosts)
443{
444/// TCP Keeper
445const char * port_name = "keeper_server.tcp_port";
446createServer(listen_host, port_name, listen_try, [&](UInt16 port)
447{
448Poco::Net::ServerSocket socket;
449auto address = socketBindListen(socket, listen_host, port);
450socket.setReceiveTimeout(Poco::Timespan{tcp_receive_timeout, 0});
451socket.setSendTimeout(Poco::Timespan{tcp_send_timeout, 0});
452servers->emplace_back(
453listen_host,
454port_name,
455"Keeper (tcp): " + address.toString(),
456std::make_unique<TCPServer>(
457new KeeperTCPHandlerFactory(
458config_getter, global_context->getKeeperDispatcher(),
459tcp_receive_timeout, tcp_send_timeout, false), server_pool, socket));
460});
461
462const char * secure_port_name = "keeper_server.tcp_port_secure";
463createServer(listen_host, secure_port_name, listen_try, [&](UInt16 port)
464{
465#if USE_SSL
466Poco::Net::SecureServerSocket socket;
467auto address = socketBindListen(socket, listen_host, port, /* secure = */ true);
468socket.setReceiveTimeout(Poco::Timespan{tcp_receive_timeout, 0});
469socket.setSendTimeout(Poco::Timespan{tcp_send_timeout, 0});
470servers->emplace_back(
471listen_host,
472secure_port_name,
473"Keeper with secure protocol (tcp_secure): " + address.toString(),
474std::make_unique<TCPServer>(
475new KeeperTCPHandlerFactory(
476config_getter, global_context->getKeeperDispatcher(),
477tcp_receive_timeout, tcp_send_timeout, true), server_pool, socket));
478#else
479UNUSED(port);
480throw Exception(ErrorCodes::SUPPORT_IS_DISABLED, "SSL support for TCP protocol is disabled because Poco library was built without NetSSL support.");
481#endif
482});
483
484const auto & config = config_getter();
485auto http_context = httpContext();
486Poco::Timespan keep_alive_timeout(config.getUInt("keep_alive_timeout", 10), 0);
487Poco::Net::HTTPServerParams::Ptr http_params = new Poco::Net::HTTPServerParams;
488http_params->setTimeout(http_context->getReceiveTimeout());
489http_params->setKeepAliveTimeout(keep_alive_timeout);
490
491/// Prometheus (if defined and not setup yet with http_port)
492port_name = "prometheus.port";
493createServer(
494listen_host,
495port_name,
496listen_try,
497[&, my_http_context = std::move(http_context)](UInt16 port) mutable
498{
499Poco::Net::ServerSocket socket;
500auto address = socketBindListen(socket, listen_host, port);
501socket.setReceiveTimeout(my_http_context->getReceiveTimeout());
502socket.setSendTimeout(my_http_context->getSendTimeout());
503auto metrics_writer = std::make_shared<KeeperPrometheusMetricsWriter>(config, "prometheus", async_metrics);
504servers->emplace_back(
505listen_host,
506port_name,
507"Prometheus: http://" + address.toString(),
508std::make_unique<HTTPServer>(
509std::move(my_http_context),
510createPrometheusMainHandlerFactory(*this, config_getter(), metrics_writer, "PrometheusHandler-factory"),
511server_pool,
512socket,
513http_params));
514});
515
516/// HTTP control endpoints
517port_name = "keeper_server.http_control.port";
518createServer(listen_host, port_name, listen_try, [&](UInt16 port) mutable
519{
520auto my_http_context = httpContext();
521Poco::Timespan my_keep_alive_timeout(config.getUInt("keep_alive_timeout", 10), 0);
522Poco::Net::HTTPServerParams::Ptr my_http_params = new Poco::Net::HTTPServerParams;
523my_http_params->setTimeout(my_http_context->getReceiveTimeout());
524my_http_params->setKeepAliveTimeout(my_keep_alive_timeout);
525
526Poco::Net::ServerSocket socket;
527auto address = socketBindListen(socket, listen_host, port);
528socket.setReceiveTimeout(my_http_context->getReceiveTimeout());
529socket.setSendTimeout(my_http_context->getSendTimeout());
530servers->emplace_back(
531listen_host,
532port_name,
533"HTTP Control: http://" + address.toString(),
534std::make_unique<HTTPServer>(
535std::move(my_http_context), createKeeperHTTPControlMainHandlerFactory(config_getter(), global_context->getKeeperDispatcher(), "KeeperHTTPControlHandler-factory"), server_pool, socket, http_params)
536);
537});
538}
539
540for (auto & server : *servers)
541{
542server.start();
543LOG_INFO(log, "Listening for {}", server.getDescription());
544}
545
546async_metrics.start();
547
548zkutil::EventPtr unused_event = std::make_shared<Poco::Event>();
549zkutil::ZooKeeperNodeCache unused_cache([] { return nullptr; });
550
551const std::string cert_path = config().getString("openSSL.server.certificateFile", "");
552const std::string key_path = config().getString("openSSL.server.privateKeyFile", "");
553
554std::vector<std::string> extra_paths = {include_from_path};
555if (!cert_path.empty())
556extra_paths.emplace_back(cert_path);
557if (!key_path.empty())
558extra_paths.emplace_back(key_path);
559
560/// ConfigReloader have to strict parameters which are redundant in our case
561auto main_config_reloader = std::make_unique<ConfigReloader>(
562config_path,
563extra_paths,
564config().getString("path", KEEPER_DEFAULT_PATH),
565std::move(unused_cache),
566unused_event,
567[&](ConfigurationPtr config, bool /* initial_loading */)
568{
569updateLevels(*config, logger());
570
571updateMemorySoftLimitInConfig(*config);
572
573if (config->has("keeper_server"))
574global_context->updateKeeperConfiguration(*config);
575
576#if USE_SSL
577CertificateReloader::instance().tryLoad(*config);
578#endif
579},
580/* already_loaded = */ false); /// Reload it right now (initial loading)
581
582SCOPE_EXIT({
583LOG_INFO(log, "Shutting down.");
584main_config_reloader.reset();
585
586async_metrics.stop();
587
588LOG_DEBUG(log, "Waiting for current connections to Keeper to finish.");
589size_t current_connections = 0;
590for (auto & server : *servers)
591{
592server.stop();
593current_connections += server.currentConnections();
594}
595
596if (current_connections)
597LOG_INFO(log, "Closed all listening sockets. Waiting for {} outstanding connections.", current_connections);
598else
599LOG_INFO(log, "Closed all listening sockets.");
600
601if (current_connections > 0)
602current_connections = waitServersToFinish(*servers, servers_lock, config().getInt("shutdown_wait_unfinished", 5));
603
604if (current_connections)
605LOG_INFO(log, "Closed connections to Keeper. But {} remain. Probably some users cannot finish their connections after context shutdown.", current_connections);
606else
607LOG_INFO(log, "Closed connections to Keeper.");
608
609global_context->shutdownKeeperDispatcher();
610
611/// Wait server pool to avoid use-after-free of destroyed context in the handlers
612server_pool.joinAll();
613
614LOG_DEBUG(log, "Destroyed global context.");
615
616if (current_connections)
617{
618LOG_INFO(log, "Will shutdown forcefully.");
619safeExit(0);
620}
621});
622
623
624buildLoggers(config(), logger());
625main_config_reloader->start();
626
627std::optional<CgroupsMemoryUsageObserver> cgroups_memory_usage_observer;
628try
629{
630auto wait_time = config().getUInt64("keeper_server.cgroups_memory_observer_wait_time", 15);
631if (wait_time != 0)
632{
633cgroups_memory_usage_observer.emplace(std::chrono::seconds(wait_time));
634/// Not calling cgroups_memory_usage_observer->setLimits() here (as for the normal ClickHouse server) because Keeper controls
635/// its memory usage by other means (via setting 'max_memory_usage_soft_limit').
636cgroups_memory_usage_observer->setOnMemoryAmountAvailableChangedFn([&]() { main_config_reloader->reload(); });
637cgroups_memory_usage_observer->startThread();
638}
639}
640catch (Exception &)
641{
642tryLogCurrentException(log, "Disabling cgroup memory observer because of an error during initialization");
643}
644
645
646LOG_INFO(log, "Ready for connections.");
647
648waitForTerminationRequest();
649
650return Application::EXIT_OK;
651}
652catch (...)
653{
654/// Poco does not provide stacktrace.
655tryLogCurrentException("Application");
656auto code = getCurrentExceptionCode();
657return code ? code : -1;
658}
659
660
661void Keeper::logRevision() const
662{
663LOG_INFO(getLogger("Application"),
664"Starting ClickHouse Keeper {} (revision: {}, git hash: {}, build id: {}), PID {}",
665VERSION_STRING,
666ClickHouseRevision::getVersionRevision(),
667git_hash.empty() ? "<unknown>" : git_hash,
668build_id.empty() ? "<unknown>" : build_id,
669getpid());
670}
671
672
673}
674