#include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int UNKNOWN_ELEMENT_IN_CONFIG; } namespace { /// Parses common configuration which is attached to any other configuration. The common configuration looks like this: /// /// true /// /// 30 void parseCommonConfig(const Poco::Util::AbstractConfiguration & config, PrometheusRequestHandlerConfig & res) { res.is_stacktrace_enabled = config.getBool("prometheus.enable_stacktrace", true); res.keep_alive_timeout = config.getUInt("keep_alive_timeout", DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT); } /// Parses a configuration like this: /// /// true /// true /// true /// true PrometheusRequestHandlerConfig parseExposeMetricsConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix) { PrometheusRequestHandlerConfig res; res.type = PrometheusRequestHandlerConfig::Type::ExposeMetrics; res.expose_metrics = config.getBool(config_prefix + ".metrics", true); res.expose_asynchronous_metrics = config.getBool(config_prefix + ".asynchronous_metrics", true); res.expose_events = config.getBool(config_prefix + ".events", true); res.expose_errors = config.getBool(config_prefix + ".errors", true); parseCommonConfig(config, res); return res; } /// Extracts a qualified table name from the config. It can be set either as /// mydb.prometheus
/// or /// mydb /// prometheus
QualifiedTableName parseTableNameFromConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix) { QualifiedTableName res; res.table = config.getString(config_prefix + ".table", "prometheus"); res.database = config.getString(config_prefix + ".database", ""); if (res.database.empty()) res = QualifiedTableName::parseFromString(res.table); if (res.database.empty()) res.database = "default"; return res; } /// Parses a configuration like this: /// /// db.time_series_table_name
PrometheusRequestHandlerConfig parseRemoteWriteConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix) { PrometheusRequestHandlerConfig res; res.type = PrometheusRequestHandlerConfig::Type::RemoteWrite; res.time_series_table_name = parseTableNameFromConfig(config, config_prefix); parseCommonConfig(config, res); return res; } /// Parses a configuration like this: /// /// db.time_series_table_name
PrometheusRequestHandlerConfig parseRemoteReadConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix) { PrometheusRequestHandlerConfig res; res.type = PrometheusRequestHandlerConfig::Type::RemoteRead; res.time_series_table_name = parseTableNameFromConfig(config, config_prefix); parseCommonConfig(config, res); return res; } /// Parses a configuration like this: /// expose_metrics /// true /// true /// true /// true /// -OR- /// remote_write /// db.time_series_table_name
PrometheusRequestHandlerConfig parseHandlerConfig(const Poco::Util::AbstractConfiguration & config, const String & config_prefix) { String type = config.getString(config_prefix + ".type"); if (type == "expose_metrics") return parseExposeMetricsConfig(config, config_prefix); if (type == "remote_write") return parseRemoteWriteConfig(config, config_prefix); if (type == "remote_read") return parseRemoteReadConfig(config, config_prefix); throw Exception( ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG, "Unknown type {} is specified in the configuration for a prometheus protocol", type); } /// Returns true if the protocol represented by a passed config can be handled. bool canBeHandled(const PrometheusRequestHandlerConfig & config, bool for_keeper) { /// The standalone ClickHouse Keeper can only expose its metrics. /// It can't handle other Prometheus protocols. return !for_keeper || (config.type == PrometheusRequestHandlerConfig::Type::ExposeMetrics); } /// Creates a writer which serializes exposing metrics. std::shared_ptr createPrometheusMetricWriter(bool for_keeper) { if (for_keeper) return std::make_unique(); return std::make_unique(); } /// Base function for making a factory for PrometheusRequestHandler. This function can return nullptr. std::shared_ptr> createPrometheusHandlerFactoryFromConfig( IServer & server, const AsynchronousMetrics & async_metrics, const PrometheusRequestHandlerConfig & config, bool for_keeper) { if (!canBeHandled(config, for_keeper)) return nullptr; auto metric_writer = createPrometheusMetricWriter(for_keeper); auto creator = [&server, &async_metrics, config, metric_writer]() -> std::unique_ptr { return std::make_unique(server, config, async_metrics, metric_writer); }; return std::make_shared>(std::move(creator)); } /// Generic function for createPrometheusHandlerFactory() and createKeeperPrometheusHandlerFactory(). HTTPRequestHandlerFactoryPtr createPrometheusHandlerFactoryImpl( IServer & server, const Poco::Util::AbstractConfiguration & config, const AsynchronousMetrics & asynchronous_metrics, const String & name, bool for_keeper) { auto factory = std::make_shared(name); if (config.has("prometheus.handlers")) { Strings keys; config.keys("prometheus.handlers", keys); for (const String & key : keys) { String prefix = "prometheus.handlers." + key; auto parsed_config = parseHandlerConfig(config, prefix + ".handler"); if (auto handler = createPrometheusHandlerFactoryFromConfig(server, asynchronous_metrics, parsed_config, for_keeper)) { handler->addFiltersFromConfig(config, prefix); factory->addHandler(handler); } } } else { auto parsed_config = parseExposeMetricsConfig(config, "prometheus"); if (auto handler = createPrometheusHandlerFactoryFromConfig(server, asynchronous_metrics, parsed_config, for_keeper)) { String endpoint = config.getString("prometheus.endpoint", "/metrics"); handler->attachStrictPath(endpoint); handler->allowGetAndHeadRequest(); factory->addHandler(handler); } } return factory; } } HTTPRequestHandlerFactoryPtr createPrometheusHandlerFactory( IServer & server, const Poco::Util::AbstractConfiguration & config, const AsynchronousMetrics & asynchronous_metrics, const String & name) { return createPrometheusHandlerFactoryImpl(server, config, asynchronous_metrics, name, /* for_keeper= */ false); } HTTPRequestHandlerFactoryPtr createPrometheusHandlerFactoryForHTTPRule( IServer & server, const Poco::Util::AbstractConfiguration & config, const String & config_prefix, const AsynchronousMetrics & asynchronous_metrics) { auto parsed_config = parseExposeMetricsConfig(config, config_prefix + ".handler"); auto handler = createPrometheusHandlerFactoryFromConfig(server, asynchronous_metrics, parsed_config, /* for_keeper= */ false); chassert(handler); /// `handler` can't be nullptr here because `for_keeper` is false. handler->addFiltersFromConfig(config, config_prefix); return handler; } HTTPRequestHandlerFactoryPtr createPrometheusHandlerFactoryForHTTPRuleDefaults( IServer & server, const Poco::Util::AbstractConfiguration & config, const AsynchronousMetrics & asynchronous_metrics) { /// The "defaults" HTTP handler should serve the prometheus exposing metrics protocol on the http port /// only if it isn't already served on its own port and if there is no section. if (!config.has("prometheus") || config.getInt("prometheus.port", 0) || config.has("prometheus.handlers")) return nullptr; auto parsed_config = parseExposeMetricsConfig(config, "prometheus"); String endpoint = config.getString("prometheus.endpoint", "/metrics"); auto handler = createPrometheusHandlerFactoryFromConfig(server, asynchronous_metrics, parsed_config, /* for_keeper= */ false); chassert(handler); /// `handler` can't be nullptr here because `for_keeper` is false. handler->attachStrictPath(endpoint); handler->allowGetAndHeadRequest(); return handler; } HTTPRequestHandlerFactoryPtr createKeeperPrometheusHandlerFactory( IServer & server, const Poco::Util::AbstractConfiguration & config, const AsynchronousMetrics & asynchronous_metrics, const String & name) { return createPrometheusHandlerFactoryImpl(server, config, asynchronous_metrics, name, /* for_keeper= */ true); } }