Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prometheus endpoint #7900

Merged
merged 14 commits into from
Dec 15, 2019
Merged
3 changes: 3 additions & 0 deletions dbms/programs/server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
set(CLICKHOUSE_SERVER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/HTTPHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/HTTPHandlerFactory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/InterserverIOHTTPHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/MetricsTransmitter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NotFoundHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PingRequestHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PrometheusMetricsWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PrometheusRequestHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ReplicasStatusHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/RootRequestHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Server.cpp
Expand Down
43 changes: 43 additions & 0 deletions dbms/programs/server/HTTPHandlerFactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "HTTPHandlerFactory.h"


namespace DB
{

HTTPRequestHandlerFactoryMain::HTTPRequestHandlerFactoryMain(IServer & server_, const std::string & name_)
: server(server_), log(&Logger::get(name_)), name(name_)
{
}

Poco::Net::HTTPRequestHandler * HTTPRequestHandlerFactoryMain::createRequestHandler(
const Poco::Net::HTTPServerRequest & request) // override
{
LOG_TRACE(log, "HTTP Request for " << name << ". "
<< "Method: "
<< request.getMethod()
<< ", Address: "
<< request.clientAddress().toString()
<< ", User-Agent: "
<< (request.has("User-Agent") ? request.get("User-Agent") : "none")
<< (request.hasContentLength() ? (", Length: " + std::to_string(request.getContentLength())) : (""))
<< ", Content Type: " << request.getContentType()
<< ", Transfer Encoding: " << request.getTransferEncoding());

for (auto & handlerFactory: child_handler_factories)
{
auto handler = handlerFactory->createRequestHandler(request);
if (handler != nullptr)
return handler;
}

if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
return new NotFoundHandler;
}

return nullptr;
}

}
128 changes: 90 additions & 38 deletions dbms/programs/server/HTTPHandlerFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,67 +9,119 @@
#include "InterserverIOHTTPHandler.h"
#include "NotFoundHandler.h"
#include "PingRequestHandler.h"
#include "PrometheusRequestHandler.h"
#include "ReplicasStatusHandler.h"
#include "RootRequestHandler.h"


namespace DB
{

template <typename HandlerType>
class HTTPRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
/// Handle request using child handlers
class HTTPRequestHandlerFactoryMain : public Poco::Net::HTTPRequestHandlerFactory
{
private:
using TThis = HTTPRequestHandlerFactoryMain;

IServer & server;
Logger * log;
std::string name;

std::vector<std::unique_ptr<Poco::Net::HTTPRequestHandlerFactory>> child_handler_factories;

public:
HTTPRequestHandlerFactory(IServer & server_, const std::string & name_) : server(server_), log(&Logger::get(name_)), name(name_)
HTTPRequestHandlerFactoryMain(IServer & server_, const std::string & name_);

Poco::Net::HTTPRequestHandler * createRequestHandler(const Poco::Net::HTTPServerRequest & request) override;

template <typename T, typename... TArgs>
TThis * addHandler(TArgs &&... args)
{
child_handler_factories.emplace_back(std::make_unique<T>(server, std::forward<TArgs>(args)...));
return this;
}
};


/// Handle POST or GET with params
template <typename HandleType>
class HTTPQueryRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
{
private:
IServer & server;

public:
HTTPQueryRequestHandlerFactory(IServer & server_) : server(server_) {}

Poco::Net::HTTPRequestHandler * createRequestHandler(const Poco::Net::HTTPServerRequest & request) override
{
LOG_TRACE(log, "HTTP Request for " << name << ". "
<< "Method: "
<< request.getMethod()
<< ", Address: "
<< request.clientAddress().toString()
<< ", User-Agent: "
<< (request.has("User-Agent") ? request.get("User-Agent") : "none")
<< (request.hasContentLength() ? (", Length: " + std::to_string(request.getContentLength())) : (""))
<< ", Content Type: " << request.getContentType()
<< ", Transfer Encoding: " << request.getTransferEncoding());

const auto & uri = request.getURI();

if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET || request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD)
{
if (uri == "/")
return new RootRequestHandler(server);
if (uri == "/ping")
return new PingRequestHandler(server);
else if (startsWith(uri, "/replicas_status"))
return new ReplicasStatusHandler(server.context());
}

if (uri.find('?') != std::string::npos || request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
return new HandlerType(server);
}

if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET || request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
return new NotFoundHandler;
}
if (request.getURI().find('?') != std::string::npos || request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
return new HandleType(server);
return nullptr;
}
};


/// Handle GET or HEAD endpoint on specified path
template <typename TGetEndpoint>
class HTTPGetRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
{
private:
IServer & server;
public:
HTTPGetRequestHandlerFactory(IServer & server_) : server(server_) {}

Poco::Net::HTTPRequestHandler * createRequestHandler(const Poco::Net::HTTPServerRequest & request) override
{
auto & method = request.getMethod();
if (!(method == Poco::Net::HTTPRequest::HTTP_GET || method == Poco::Net::HTTPRequest::HTTP_HEAD))
return nullptr;

auto & uri = request.getURI();
bool uri_match = TGetEndpoint::strict_path ? uri == TGetEndpoint::path : startsWith(uri, TGetEndpoint::path);
if (uri_match)
return new typename TGetEndpoint::HandleType(server);

return nullptr;
}
};

using HTTPHandlerFactory = HTTPRequestHandlerFactory<HTTPHandler>;
using InterserverIOHTTPHandlerFactory = HTTPRequestHandlerFactory<InterserverIOHTTPHandler>;

struct RootEndpoint
{
static constexpr auto path = "/";
static constexpr auto strict_path = true;
using HandleType = RootRequestHandler;
};

struct PingEndpoint
{
static constexpr auto path = "/ping";
static constexpr auto strict_path = true;
using HandleType = PingRequestHandler;
};

struct ReplicasStatusEndpoint
{
static constexpr auto path = "/replicas_status";
static constexpr auto strict_path = false;
using HandleType = ReplicasStatusHandler;
};

using HTTPRootRequestHandlerFactory = HTTPGetRequestHandlerFactory<RootEndpoint>;
using HTTPPingRequestHandlerFactory = HTTPGetRequestHandlerFactory<PingEndpoint>;
using HTTPReplicasStatusRequestHandlerFactory = HTTPGetRequestHandlerFactory<ReplicasStatusEndpoint>;

template <typename HandleType>
HTTPRequestHandlerFactoryMain * createDefaultHandlerFatory(IServer & server, const std::string & name)
{
auto handlerFactory = new HTTPRequestHandlerFactoryMain(server, name);
handlerFactory->addHandler<HTTPRootRequestHandlerFactory>()
->addHandler<HTTPPingRequestHandlerFactory>()
->addHandler<HTTPReplicasStatusRequestHandlerFactory>()
->addHandler<HTTPQueryRequestHandlerFactory<HandleType>>();
return handlerFactory;
}


}
90 changes: 90 additions & 0 deletions dbms/programs/server/PrometheusMetricsWriter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "PrometheusMetricsWriter.h"

#include <IO/WriteHelpers.h>

namespace
{

template <typename T>
void writeOutLine(DB::WriteBuffer & wb, T && val)
{
DB::writeText(std::forward<T>(val), wb);
DB::writeChar('\n', wb);
}

template <typename T, typename... TArgs>
void writeOutLine(DB::WriteBuffer & wb, T && val, TArgs &&... args)
{
DB::writeText(std::forward<T>(val), wb);
DB::writeChar(' ', wb);
writeOutLine(wb, std::forward<TArgs>(args)...);
}

}


namespace DB
{

PrometheusMetricsWriter::PrometheusMetricsWriter(
const Poco::Util::AbstractConfiguration & config, const std::string & config_name,
const AsynchronousMetrics & async_metrics_)
: async_metrics(async_metrics_)
, send_events(config.getBool(config_name + ".events", true))
, send_metrics(config.getBool(config_name + ".metrics", true))
, send_asynchronous_metrics(config.getBool(config_name + ".asynchronous_metrics", true))
{
}

void PrometheusMetricsWriter::write(WriteBuffer & wb) const
{
if (send_events)
{
for (size_t i = 0, end = ProfileEvents::end(); i < end; ++i)
{
const auto counter = ProfileEvents::global_counters[i].load(std::memory_order_relaxed);

std::string metric_name{ProfileEvents::getName(static_cast<ProfileEvents::Event>(i))};
std::string metric_doc{ProfileEvents::getDocumentation(static_cast<ProfileEvents::Event>(i))};

std::string key{profile_events_prefix + metric_name};

writeOutLine(wb, "# HELP", key, metric_doc);
writeOutLine(wb, "# TYPE", key, "counter");
writeOutLine(wb, key, counter);
}
}

if (send_metrics)
{
for (size_t i = 0, end = CurrentMetrics::end(); i < end; ++i)
{
const auto value = CurrentMetrics::values[i].load(std::memory_order_relaxed);

std::string metric_name{CurrentMetrics::getName(static_cast<CurrentMetrics::Metric>(i))};
std::string metric_doc{CurrentMetrics::getDocumentation(static_cast<CurrentMetrics::Metric>(i))};

std::string key{current_metrics_prefix + metric_name};

writeOutLine(wb, "# HELP", key, metric_doc);
writeOutLine(wb, "# TYPE", key, "gauge");
writeOutLine(wb, key, value);
}
}

if (send_asynchronous_metrics)
{
auto async_metrics_values = async_metrics.getValues();
for (const auto & name_value : async_metrics_values)
{
std::string key{asynchronous_metrics_prefix + name_value.first};
auto value = name_value.second;

// TODO: add HELP section? asynchronous_metrics contains only key and value
writeOutLine(wb, "# TYPE", key, "gauge");
writeOutLine(wb, key, value);
}
}
}

}
36 changes: 36 additions & 0 deletions dbms/programs/server/PrometheusMetricsWriter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <string>

#include <Interpreters/AsynchronousMetrics.h>

#include <IO/WriteBuffer.h>

#include <Poco/Util/AbstractConfiguration.h>

namespace DB
{

/// Write metrics in Prometheus format
class PrometheusMetricsWriter
{
public:
PrometheusMetricsWriter(
const Poco::Util::AbstractConfiguration & config, const std::string & config_name,
const AsynchronousMetrics & async_metrics_);

void write(WriteBuffer & wb) const;

private:
const AsynchronousMetrics & async_metrics;

const bool send_events;
const bool send_metrics;
const bool send_asynchronous_metrics;

static inline constexpr auto profile_events_prefix = "ClickHouseProfileEvents";
static inline constexpr auto current_metrics_prefix = "ClickHouseMetrics";
static inline constexpr auto asynchronous_metrics_prefix = "ClickHouseAsyncMetrics";
};

}
42 changes: 42 additions & 0 deletions dbms/programs/server/PrometheusRequestHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "PrometheusRequestHandler.h"

#include <IO/HTTPCommon.h>

#include <Common/Exception.h>

#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>

#include <Common/ProfileEvents.h>
#include <Common/CurrentMetrics.h>

#include <IO/WriteBufferFromHTTPServerResponse.h>


namespace DB
{

void PrometheusRequestHandler::handleRequest(
Poco::Net::HTTPServerRequest & request,
Poco::Net::HTTPServerResponse & response)
{
try
{
const auto & config = server.config();
unsigned keep_alive_timeout = config.getUInt("keep_alive_timeout", 10);

setResponseDefaultHeaders(response, keep_alive_timeout);

response.setContentType("text/plain; version=0.0.4; charset=UTF-8");

auto wb = WriteBufferFromHTTPServerResponse(request, response, keep_alive_timeout);
metrics_writer.write(wb);
wb.finalize();
}
catch (...)
{
tryLogCurrentException("PrometheusRequestHandler");
}
}

}