Compare commits

...

2 Commits

Author SHA1 Message Date
An Tao
cedeeb59f4
Add a plugin for prometheus (#1632) 2023-09-04 10:16:44 +08:00
An Tao
4e5638fdcd
Add a method to HttpRequest to access the matched routing parameters (#1765) 2023-09-04 10:13:14 +08:00
22 changed files with 1204 additions and 69 deletions

View File

@ -230,12 +230,18 @@ endif (BUILD_BROTLI)
set(DROGON_SOURCES set(DROGON_SOURCES
lib/src/AOPAdvice.cc lib/src/AOPAdvice.cc
lib/src/AccessLogger.cc
lib/src/CacheFile.cc lib/src/CacheFile.cc
lib/src/ConfigAdapterManager.cc
lib/src/ConfigLoader.cc lib/src/ConfigLoader.cc
lib/src/Cookie.cc lib/src/Cookie.cc
lib/src/DrClassMap.cc lib/src/DrClassMap.cc
lib/src/DrTemplateBase.cc lib/src/DrTemplateBase.cc
lib/src/FiltersFunction.cc lib/src/FiltersFunction.cc
lib/src/FixedWindowRateLimiter.cc
lib/src/GlobalFilters.cc
lib/src/Histogram.cc
lib/src/Hodor.cc
lib/src/HttpAppFrameworkImpl.cc lib/src/HttpAppFrameworkImpl.cc
lib/src/HttpBinder.cc lib/src/HttpBinder.cc
lib/src/HttpClientImpl.cc lib/src/HttpClientImpl.cc
@ -251,33 +257,29 @@ set(DROGON_SOURCES
lib/src/HttpUtils.cc lib/src/HttpUtils.cc
lib/src/HttpViewData.cc lib/src/HttpViewData.cc
lib/src/IntranetIpFilter.cc lib/src/IntranetIpFilter.cc
lib/src/JsonConfigAdapter.cc
lib/src/ListenerManager.cc lib/src/ListenerManager.cc
lib/src/LocalHostFilter.cc lib/src/LocalHostFilter.cc
lib/src/MultiPart.cc lib/src/MultiPart.cc
lib/src/NotFound.cc lib/src/NotFound.cc
lib/src/PluginsManager.cc lib/src/PluginsManager.cc
lib/src/PromExporter.cc
lib/src/RangeParser.cc lib/src/RangeParser.cc
lib/src/SecureSSLRedirector.cc lib/src/RateLimiter.cc
lib/src/GlobalFilters.cc
lib/src/AccessLogger.cc
lib/src/RealIpResolver.cc lib/src/RealIpResolver.cc
lib/src/SecureSSLRedirector.cc
lib/src/SessionManager.cc lib/src/SessionManager.cc
lib/src/SlashRemover.cc
lib/src/SlidingWindowRateLimiter.cc
lib/src/StaticFileRouter.cc lib/src/StaticFileRouter.cc
lib/src/TaskTimeoutFlag.cc lib/src/TaskTimeoutFlag.cc
lib/src/TokenBucketRateLimiter.cc
lib/src/Utilities.cc lib/src/Utilities.cc
lib/src/WebSocketClientImpl.cc lib/src/WebSocketClientImpl.cc
lib/src/WebSocketConnectionImpl.cc lib/src/WebSocketConnectionImpl.cc
lib/src/WebsocketControllersRouter.cc lib/src/WebsocketControllersRouter.cc
lib/src/RateLimiter.cc lib/src/YamlConfigAdapter.cc
lib/src/FixedWindowRateLimiter.cc lib/src/drogon_test.cc)
lib/src/SlidingWindowRateLimiter.cc
lib/src/TokenBucketRateLimiter.cc
lib/src/Hodor.cc
lib/src/SlashRemover.cc
lib/src/drogon_test.cc
lib/src/ConfigAdapterManager.cc
lib/src/JsonConfigAdapter.cc
lib/src/YamlConfigAdapter.cc)
set(private_headers set(private_headers
lib/src/AOPAdvice.h lib/src/AOPAdvice.h
lib/src/CacheFile.h lib/src/CacheFile.h
@ -689,10 +691,23 @@ set(DROGON_UTIL_HEADERS
lib/inc/drogon/utils/FunctionTraits.h lib/inc/drogon/utils/FunctionTraits.h
lib/inc/drogon/utils/HttpConstraint.h lib/inc/drogon/utils/HttpConstraint.h
lib/inc/drogon/utils/OStringStream.h lib/inc/drogon/utils/OStringStream.h
lib/inc/drogon/utils/Utilities.h) lib/inc/drogon/utils/Utilities.h
lib/inc/drogon/utils/monitoring.h)
install(FILES ${DROGON_UTIL_HEADERS} install(FILES ${DROGON_UTIL_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils) DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils)
set(DROGON_MONITORING_HEADERS
lib/inc/drogon/utils/monitoring/Counter.h
lib/inc/drogon/utils/monitoring/Metric.h
lib/inc/drogon/utils/monitoring/Registry.h
lib/inc/drogon/utils/monitoring/Collector.h
lib/inc/drogon/utils/monitoring/Sample.h
lib/inc/drogon/utils/monitoring/Gauge.h
lib/inc/drogon/utils/monitoring/Histogram.h)
install(FILES ${DROGON_MONITORING_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils/monitoring)
set(DROGON_PLUGIN_HEADERS set(DROGON_PLUGIN_HEADERS
lib/inc/drogon/plugins/Plugin.h lib/inc/drogon/plugins/Plugin.h
lib/inc/drogon/plugins/SecureSSLRedirector.h lib/inc/drogon/plugins/SecureSSLRedirector.h
@ -700,7 +715,8 @@ set(DROGON_PLUGIN_HEADERS
lib/inc/drogon/plugins/RealIpResolver.h lib/inc/drogon/plugins/RealIpResolver.h
lib/inc/drogon/plugins/Hodor.h lib/inc/drogon/plugins/Hodor.h
lib/inc/drogon/plugins/SlashRemover.h lib/inc/drogon/plugins/SlashRemover.h
lib/inc/drogon/plugins/GlobalFilters.h) lib/inc/drogon/plugins/GlobalFilters.h
lib/inc/drogon/plugins/PromExporter.h)
install(FILES ${DROGON_PLUGIN_HEADERS} install(FILES ${DROGON_PLUGIN_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/plugins) DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/plugins)
@ -712,7 +728,8 @@ target_sources(${PROJECT_NAME} PRIVATE
${ORM_HEADERS} ${ORM_HEADERS}
${DROGON_UTIL_HEADERS} ${DROGON_UTIL_HEADERS}
${DROGON_PLUGIN_HEADERS} ${DROGON_PLUGIN_HEADERS}
${NOSQL_HEADERS}) ${NOSQL_HEADERS}
${DROGON_MONITORING_HEADERS})
source_group("Public API" source_group("Public API"
FILES FILES
@ -720,7 +737,8 @@ source_group("Public API"
${ORM_HEADERS} ${ORM_HEADERS}
${DROGON_UTIL_HEADERS} ${DROGON_UTIL_HEADERS}
${DROGON_PLUGIN_HEADERS} ${DROGON_PLUGIN_HEADERS}
${NOSQL_HEADERS}) ${NOSQL_HEADERS}
${DROGON_MONITORING_HEADERS})
source_group("Private Headers" source_group("Private Headers"
FILES FILES
${private_headers}) ${private_headers})

View File

@ -244,19 +244,19 @@
//0 means cache forever, the negative value means no cache //0 means cache forever, the negative value means no cache
"static_files_cache_time": 5, "static_files_cache_time": 5,
//simple_controllers_map: Used to configure mapping from path to simple controller //simple_controllers_map: Used to configure mapping from path to simple controller
"simple_controllers_map": [ //"simple_controllers_map": [
{ // {
"path": "/path/name", // "path": "/path/name",
"controller": "controllerClassName", // "controller": "controllerClassName",
"http_methods": [ // "http_methods": [
"get", // "get",
"post" // "post"
], // ],
"filters": [ // "filters": [
"FilterClassName" // "FilterClassName"
] // ]
} // }
], //],
//idle_connection_timeout: Defaults to 60 seconds, the lifetime //idle_connection_timeout: Defaults to 60 seconds, the lifetime
//of the connection without read or write //of the connection without read or write
"idle_connection_timeout": 60, "idle_connection_timeout": 60,
@ -307,16 +307,13 @@
"plugins": [ "plugins": [
{ {
//name: The class name of the plugin //name: The class name of the plugin
//"name": "drogon::plugin::SecureSSLRedirector", "name": "drogon::plugin::PromExporter",
//dependencies: Plugins that the plugin depends on. It can be commented out //dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [], "dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin. //config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out //It can be commented out
"config": { "config": {
"ssl_redirect_exempt": [ "path": "/metrics"
".*\\.jpg"
],
"secure_ssl_host": "localhost:8849"
} }
} }
], ],

View File

@ -115,6 +115,16 @@ class HttpBinder : public HttpBinderBase
const HttpRequestPtr &req, const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override std::function<void(const HttpResponsePtr &)> &&callback) override
{ {
if (!pathArguments.empty())
{
std::vector<std::string> args;
args.reserve(pathArguments.size());
for (auto &arg : pathArguments)
{
args.emplace_back(arg);
}
req->setRoutingParameters(std::move(args));
}
run(pathArguments, req, std::move(callback)); run(pathArguments, req, std::move(callback));
} }

View File

@ -247,6 +247,13 @@ class DROGON_EXPORT HttpRequest
matchedPathPatternLength()); matchedPathPatternLength());
} }
/// Get the matched path pattern after routing (including matched parameters
/// in the query string)
virtual const std::vector<std::string> &getRoutingParameters() const = 0;
/// This method usually is called by the framework.
virtual void setRoutingParameters(std::vector<std::string> &&params) = 0;
virtual const char *matchedPathPatternData() const = 0; virtual const char *matchedPathPatternData() const = 0;
virtual size_t matchedPathPatternLength() const = 0; virtual size_t matchedPathPatternLength() const = 0;

View File

@ -33,6 +33,7 @@
#include <drogon/plugins/Hodor.h> #include <drogon/plugins/Hodor.h>
#include <drogon/plugins/SlashRemover.h> #include <drogon/plugins/SlashRemover.h>
#include <drogon/plugins/GlobalFilters.h> #include <drogon/plugins/GlobalFilters.h>
#include <drogon/plugins/PromExporter.h>
#include <drogon/IntranetIpFilter.h> #include <drogon/IntranetIpFilter.h>
#include <drogon/LocalHostFilter.h> #include <drogon/LocalHostFilter.h>
#include <drogon/Cookie.h> #include <drogon/Cookie.h>

View File

@ -0,0 +1,98 @@
/**
* @file PromExporter.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
#include <drogon/utils/monitoring/Registry.h>
#include <drogon/utils/monitoring/Collector.h>
#include <memory>
#include <mutex>
namespace drogon
{
namespace plugin
{
/**
* @brief The PromExporter plugin implements a prometheus exporter.
* The json configuration is as follows:
* @code
{
"name": "drogon::plugin::PromExporter",
"dependencies": [],
"config": {
// The path of the metrics. the default value is "/metrics".
"path": "/metrics",
// The list of collectors.
"collectors":[
{
// The name of the collector.
"name": "http_requests_total",
// The help message of the collector.
"help": "The total number of http requests",
// The type of the collector. The default value is "counter".
// The other possible value is as following:
// "gauge", "histogram".
"type": "counter",
// The labels of the collector.
"labels": ["method", "status"]
}
]
}
}
@endcode
* */
class DROGON_EXPORT PromExporter
: public drogon::Plugin<PromExporter>,
public std::enable_shared_from_this<PromExporter>,
public drogon::monitoring::Registry
{
public:
PromExporter()
{
}
void initAndStart(const Json::Value &config) override;
void shutdown() override
{
}
~PromExporter() override
{
}
void registerCollector(
const std::shared_ptr<drogon::monitoring::CollectorBase> &collector)
override;
std::shared_ptr<drogon::monitoring::CollectorBase> getCollector(
const std::string &name) const noexcept(false);
template <typename T>
std::shared_ptr<drogon::monitoring::Collector<T>> getCollector(
const std::string &name) const
{
return std::dynamic_pointer_cast<drogon::monitoring::Collector<T>>(
getCollector(name));
}
private:
mutable std::mutex mutex_;
std::unordered_map<std::string,
std::shared_ptr<drogon::monitoring::CollectorBase>>
collectors_;
std::string path_{"/metrics"};
std::string exportMetrics();
};
} // namespace plugin
} // namespace drogon

View File

@ -0,0 +1,18 @@
/**
*
* monitoring.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <drogon/utils/monitoring/Registry.h>
#include <drogon/utils/monitoring/Collector.h>
#include <drogon/utils/monitoring/Sample.h>

View File

@ -0,0 +1,132 @@
/**
*
* Collector.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <drogon/utils/monitoring/Sample.h>
#include <drogon/utils/monitoring/Metric.h>
#include <drogon/utils/monitoring/Registry.h>
#include <string>
#include <string_view>
#include <vector>
#include <mutex>
#include <map>
#include <algorithm>
#include <memory>
namespace drogon
{
namespace monitoring
{
struct SamplesGroup
{
std::shared_ptr<Metric> metric;
std::vector<Sample> samples;
};
class CollectorBase : public std::enable_shared_from_this<CollectorBase>
{
public:
virtual ~CollectorBase() = default;
virtual std::vector<SamplesGroup> collect() const = 0;
virtual const std::string &name() const = 0;
virtual const std::string &help() const = 0;
virtual const std::string_view type() const = 0;
};
/**
* @brief The Collector class template is used to collect samples from a group
* of metric.
*/
template <typename T>
class Collector : public CollectorBase
{
public:
Collector(const std::string &name,
const std::string &help,
const std::vector<std::string> &labelNames)
: name_(name), help_(help), labelsNames_(labelNames)
{
}
const std::shared_ptr<T> &metric(
const std::vector<std::string> &labelValues) noexcept(false)
{
if (labelValues.size() != labelsNames_.size())
{
throw std::runtime_error(
"The number of label values is not equal to the number of "
"label names!");
}
std::lock_guard<std::mutex> guard(mutex_);
auto iter = metrics_.find(labelValues);
if (iter != metrics_.end())
{
return iter->second;
}
auto metric = std::make_shared<T>(name_, labelsNames_, labelValues);
metrics_[labelValues] = metric;
return metrics_[labelValues];
}
std::vector<SamplesGroup> collect() const override
{
std::lock_guard<std::mutex> guard(mutex_);
std::vector<SamplesGroup> samples;
for (auto &pair : metrics_)
{
SamplesGroup samplesGroup;
auto &metric = pair.second;
samplesGroup.metric = metric;
auto metricSamples = metric->collect();
samplesGroup.samples = std::move(metricSamples);
samples.emplace_back(std::move(samplesGroup));
}
return samples;
}
const std::string &name() const override
{
return name_;
}
const std::string &help() const override
{
return help_;
}
const std::string_view type() const override
{
return T::type();
}
void registerTo(Registry &registry)
{
registry.registerCollector(shared_from_this());
}
const std::vector<std::string> &labelsNames() const
{
return labelsNames_;
}
private:
const std::string name_;
const std::string help_;
const std::vector<std::string> labelsNames_;
std::map<std::vector<std::string>, std::shared_ptr<T>> metrics_;
mutable std::mutex mutex_;
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,82 @@
/**
*
* Counter.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <string_view>
#include <mutex>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a counter metric.
* */
class Counter : public Metric
{
public:
Counter(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: Metric(name, labelNames, labelValues)
{
}
std::vector<Sample> collect() const override
{
Sample s;
s.name = name_;
{
std::lock_guard<std::mutex> lock(mutex_);
s.value = value_;
}
return {s};
}
/**
* Increment the counter by 1.
* */
void increment()
{
std::lock_guard<std::mutex> lock(mutex_);
value_++;
}
/**
* Increment the counter by the given value.
* */
void increment(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += value;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = 0;
}
static std::string_view type()
{
return "counter";
}
private:
mutable std::mutex mutex_;
double value_{0};
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,109 @@
/**
*
* Gauge.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Metric.h>
#include <string_view>
#include <atomic>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a gauge metric.
* */
class Gauge : public Metric
{
public:
/**
* Construct a gauge metric with a name and a help string.
* */
Gauge(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: Metric(name, labelNames, labelValues)
{
}
std::vector<Sample> collect() const override
{
Sample s;
std::lock_guard<std::mutex> lock(mutex_);
s.name = name_;
s.value = value_;
s.timestamp = timestamp_;
return {s};
}
/**
* Increment the counter by 1.
* */
void increment()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += 1;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ -= 1;
}
void decrement(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ -= value;
}
/**
* Increment the counter by the given value.
* */
void increment(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ += value;
}
void reset()
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = 0;
}
void set(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
value_ = value;
}
static std::string_view type()
{
return "counter";
}
void setToCurrentTime()
{
std::lock_guard<std::mutex> lock(mutex_);
timestamp_ = trantor::Date::now();
}
private:
mutable std::mutex mutex_;
double value_{0};
trantor::Date timestamp_{0};
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,122 @@
/**
*
* Histogram.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/exports.h>
#include <drogon/utils/monitoring/Metric.h>
#include <trantor/net/EventLoopThread.h>
#include <string_view>
#include <atomic>
#include <mutex>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a counter metric.
* */
class DROGON_EXPORT Histogram : public Metric
{
public:
struct TimeBucket
{
std::vector<uint64_t> buckets;
uint64_t count{0};
double sum{0};
};
Histogram(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues,
const std::vector<double> &bucketBoundaries,
const std::chrono::duration<double> &maxAge,
uint64_t timeBucketsCount,
trantor::EventLoop *loop = nullptr) noexcept(false)
: Metric(name, labelNames, labelValues),
maxAge_(maxAge),
timeBucketCount_(timeBucketsCount),
bucketBoundaries_(bucketBoundaries)
{
if (loop == nullptr)
{
loopThreadPtr_ = std::make_unique<trantor::EventLoopThread>();
loopPtr_ = loopThreadPtr_->getLoop();
loopThreadPtr_->run();
}
else
{
loopPtr_ = loop;
}
if (maxAge > std::chrono::seconds(0))
{
if (timeBucketsCount == 0)
{
throw std::runtime_error(
"timeBucketsCount must be greater than 0");
}
}
timeBuckets_.emplace_back();
// check the bucket boundaries are sorted
for (size_t i = 1; i < bucketBoundaries.size(); i++)
{
if (bucketBoundaries[i] <= bucketBoundaries[i - 1])
{
throw std::runtime_error(
"The bucket boundaries must be sorted");
}
}
}
void observe(double value);
std::vector<Sample> collect() const override;
~Histogram() override
{
if (timerId_ != trantor::InvalidTimerId)
{
loopPtr_->invalidateTimer(timerId_);
}
}
static std::string_view type()
{
return "histogram";
}
private:
std::deque<TimeBucket> timeBuckets_;
std::unique_ptr<trantor::EventLoopThread> loopThreadPtr_;
trantor::EventLoop *loopPtr_{nullptr};
mutable std::mutex mutex_;
std::chrono::duration<double> maxAge_;
trantor::TimerId timerId_{trantor::InvalidTimerId};
size_t timeBucketCount_{0};
const std::vector<double> bucketBoundaries_;
void rotateTimeBuckets()
{
std::lock_guard<std::mutex> guard(mutex_);
TimeBucket bucket;
bucket.buckets.resize(bucketBoundaries_.size() + 1);
timeBuckets_.emplace_back(std::move(bucket));
if (timeBuckets_.size() > timeBucketCount_)
{
auto expiredTimeBucket = timeBuckets_.front();
timeBuckets_.erase(timeBuckets_.begin());
}
}
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,77 @@
/**
*
* Metric.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/utils/monitoring/Sample.h>
#include <string>
#include <vector>
#include <memory>
#include <stdexcept>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a metric.
* */
class Metric : public std::enable_shared_from_this<Metric>
{
public:
/**
* Construct a metric with a name and a help string.
* */
Metric(const std::string &name,
const std::vector<std::string> &labelNames,
const std::vector<std::string> &labelValues) noexcept(false)
: name_(name)
{
if (labelNames.size() != labelValues.size())
{
throw std::runtime_error(
"The number of label names is not equal to the number of label "
"values!");
}
labels_.resize(labelNames.size());
for (size_t i = 0; i < labelNames.size(); i++)
{
labels_[i].first = labelNames[i];
labels_[i].second = labelValues[i];
}
};
const std::string &name() const
{
return name_;
}
const std::vector<std::pair<std::string, std::string>> &labels() const
{
return labels_;
}
virtual ~Metric() = default;
virtual std::vector<Sample> collect() const = 0;
protected:
const std::string name_;
std::vector<std::pair<std::string, std::string>> labels_;
};
using MetricPtr = std::shared_ptr<Metric>;
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,35 @@
/**
*
* Registry.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <memory>
namespace drogon
{
namespace monitoring
{
class CollectorBase;
/**
* This class is used to register metrics.
* */
class Registry
{
public:
virtual ~Registry() = default;
virtual void registerCollector(
const std::shared_ptr<CollectorBase> &collector) = 0;
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,35 @@
/**
*
* Sample.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <trantor/utils/Date.h>
#include <vector>
#include <string>
namespace drogon
{
namespace monitoring
{
/**
* This class is used to collect samples for a metric.
* */
struct Sample
{
double value{0};
trantor::Date timestamp{0};
std::string name;
std::vector<std::pair<std::string, std::string>> exLabels;
};
} // namespace monitoring
} // namespace drogon

View File

@ -0,0 +1,75 @@
/**
*
* StopWatch.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <chrono>
#include <functional>
#include <assert.h>
namespace drogon
{
/**
* @brief This class is used to measure the elapsed time.
*/
class StopWatch
{
public:
StopWatch() : start_(std::chrono::steady_clock::now())
{
}
~StopWatch()
{
}
/**
* @brief Reset the start time.
*/
void reset()
{
start_ = std::chrono::steady_clock::now();
}
/**
* @brief Get the elapsed time in seconds.
*/
double elapsed() const
{
return std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::steady_clock::now() - start_)
.count();
}
private:
std::chrono::steady_clock::time_point start_;
};
class LifeTimeWatch
{
public:
LifeTimeWatch(std::function<void(double)> callback)
: stopWatch_(), callback_(std::move(callback))
{
assert(callback_);
}
~LifeTimeWatch()
{
callback_(stopWatch_.elapsed());
}
private:
StopWatch stopWatch_;
std::function<void(double)> callback_;
};
} // namespace drogon

80
lib/src/Histogram.cc Normal file
View File

@ -0,0 +1,80 @@
#include <drogon/utils/monitoring/Histogram.h>
using namespace drogon;
using namespace drogon::monitoring;
void Histogram::observe(double value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (maxAge_ > std::chrono::seconds(0) &&
timerId_ == trantor::InvalidTimerId)
{
std::weak_ptr<Histogram> weakPtr =
std::dynamic_pointer_cast<Histogram>(shared_from_this());
timerId_ = loopPtr_->runEvery(maxAge_ / timeBucketCount_, [weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
thisPtr->rotateTimeBuckets();
});
}
auto &currentBucket = timeBuckets_.back();
currentBucket.sum += value;
currentBucket.count += 1;
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
if (value <= bucketBoundaries_[i])
{
currentBucket.buckets[i] += 1;
break;
}
}
if (value > bucketBoundaries_.back())
{
currentBucket.buckets.back() += 1;
}
}
std::vector<Sample> Histogram::collect() const
{
std::vector<Sample> samples;
std::lock_guard<std::mutex> guard(mutex_);
size_t count{0};
for (size_t i = 0; i < bucketBoundaries_.size(); i++)
{
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets[i];
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le",
std::to_string(bucketBoundaries_[i]));
sample.value = count;
samples.emplace_back(std::move(sample));
}
Sample sample;
for (auto &bucket : timeBuckets_)
{
count += bucket.buckets.back();
}
sample.name = name_ + "_bucket";
sample.exLabels.emplace_back("le", "+Inf");
sample.value = count;
samples.emplace_back(std::move(sample));
double sum{0};
uint64_t totalCount{0};
for (auto &bucket : timeBuckets_)
{
sum += bucket.sum;
totalCount += bucket.count;
}
Sample sumSample;
sumSample.name = name_ + "_sum";
sumSample.value = sum;
samples.emplace_back(std::move(sumSample));
Sample countSample;
countSample.name = name_ + "_count";
countSample.value = totalCount;
samples.emplace_back(std::move(countSample));
return samples;
}

View File

@ -524,6 +524,42 @@ void HttpControllersRouter::route(
} }
return; return;
} }
std::vector<std::string> params;
for (size_t j = 1; j < result.size(); ++j)
{
if (!result[j].matched)
continue;
size_t place = j;
if (j <= binder->parameterPlaces_.size())
{
place = binder->parameterPlaces_[j - 1];
}
if (place > params.size())
params.resize(place);
params[place - 1] = result[j].str();
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
}
if (!binder->queryParametersPlaces_.empty())
{
auto &queryPara = req->getParameters();
for (auto const &paraPlace : binder->queryParametersPlaces_)
{
auto place = paraPlace.second;
if (place > params.size())
params.resize(place);
auto iter = queryPara.find(paraPlace.first);
if (iter != queryPara.end())
{
params[place - 1] = iter->second;
}
else
{
params[place - 1] = std::string{};
}
}
}
req->setRoutingParameters(std::move(params));
if (!postRoutingObservers_.empty()) if (!postRoutingObservers_.empty())
{ {
for (auto &observer : postRoutingObservers_) for (auto &observer : postRoutingObservers_)
@ -639,41 +675,11 @@ void HttpControllersRouter::doControllerHandler(
} }
} }
std::deque<std::string> params(ctrlBinderPtr->parameterPlaces_.size()); auto &paramsVector = req->getRoutingParameters();
std::deque<std::string> params(paramsVector.size());
for (size_t j = 1; j < matchResult.size(); ++j) for (int i = 0; i < paramsVector.size(); i++)
{ {
if (!matchResult[j].matched) params[i] = paramsVector[i];
continue;
size_t place = j;
if (j <= ctrlBinderPtr->parameterPlaces_.size())
{
place = ctrlBinderPtr->parameterPlaces_[j - 1];
}
if (place > params.size())
params.resize(place);
params[place - 1] = matchResult[j].str();
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
}
if (!ctrlBinderPtr->queryParametersPlaces_.empty())
{
auto &queryPara = req->getParameters();
for (auto const &paraPlace : ctrlBinderPtr->queryParametersPlaces_)
{
auto place = paraPlace.second;
if (place > params.size())
params.resize(place);
auto iter = queryPara.find(paraPlace.first);
if (iter != queryPara.end())
{
params[place - 1] = iter->second;
}
else
{
params[place - 1] = std::string{};
}
}
} }
ctrlBinderPtr->binderPtr_->handleHttpRequest( ctrlBinderPtr->binderPtr_->handleHttpRequest(
params, params,

View File

@ -583,6 +583,7 @@ void HttpRequestImpl::swap(HttpRequestImpl &that) noexcept
swap(loop_, that.loop_); swap(loop_, that.loop_);
swap(flagForParsingContentType_, that.flagForParsingContentType_); swap(flagForParsingContentType_, that.flagForParsingContentType_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_); swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
swap(routingParams_, that.routingParams_);
} }
const char *HttpRequestImpl::versionString() const const char *HttpRequestImpl::versionString() const

View File

@ -79,6 +79,7 @@ class HttpRequestImpl : public HttpRequest
keepAlive_ = true; keepAlive_ = true;
jsonParsingErrorPtr_.reset(); jsonParsingErrorPtr_.reset();
peerCertificate_.reset(); peerCertificate_.reset();
routingParams_.clear();
} }
trantor::EventLoop *getLoop() trantor::EventLoop *getLoop()
@ -143,6 +144,16 @@ class HttpRequestImpl : public HttpRequest
} }
} }
const std::vector<std::string> &getRoutingParameters() const override
{
return routingParams_;
}
void setRoutingParameters(std::vector<std::string> &&params)
{
routingParams_ = std::move(params);
}
void setPath(const std::string &path) override void setPath(const std::string &path) override
{ {
path_ = path; path_ = path;
@ -614,6 +625,7 @@ class HttpRequestImpl : public HttpRequest
bool keepAlive_{true}; bool keepAlive_{true};
bool isOnSecureConnection_{false}; bool isOnSecureConnection_{false};
bool passThrough_{false}; bool passThrough_{false};
std::vector<std::string> routingParams_;
protected: protected:
std::string content_; std::string content_;

207
lib/src/PromExporter.cc Normal file
View File

@ -0,0 +1,207 @@
#include <drogon/plugins/PromExporter.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/utils/monitoring/Counter.h>
#include <drogon/utils/monitoring/Gauge.h>
#include <drogon/utils/monitoring/Histogram.h>
#include <drogon/utils/monitoring/Collector.h>
using namespace drogon;
using namespace drogon::monitoring;
using namespace drogon::plugin;
void PromExporter::initAndStart(const Json::Value &config)
{
path_ = config.get("path", path_).asString();
LOG_ERROR << path_;
auto &app = drogon::app();
std::weak_ptr<PromExporter> weakPtr = shared_from_this();
app.registerHandler(
path_,
[weakPtr](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
{
auto resp = HttpResponse::newNotFoundResponse();
callback(resp);
return;
}
auto resp = HttpResponse::newHttpResponse();
resp->setBody(thisPtr->exportMetrics());
resp->setExpiredTime(5);
callback(resp);
},
{Get, Options},
"PromExporter");
if (config.isMember("collectors"))
{
std::lock_guard<std::mutex> guard(mutex_);
auto &collectors = config["collectors"];
if (collectors.isArray())
{
for (auto const &collector : collectors)
{
if (collector.isObject())
{
auto name = collector["name"].asString();
auto type = collector["type"].asString();
auto help = collector["help"].asString();
auto labels = collector["labels"];
if (labels.isArray())
{
std::vector<std::string> labelNames;
for (auto const &label : labels)
{
if (label.isString())
{
labelNames.push_back(label.asString());
}
else
{
LOG_ERROR << "label name must be a string!";
}
}
if (type == "counter")
{
auto counterCollector =
std::make_shared<Collector<Counter>>(
name, help, labelNames);
collectors_.insert(
std::make_pair(name, counterCollector));
}
else if (type == "gauge")
{
auto gaugeCollector =
std::make_shared<Collector<Gauge>>(name,
help,
labelNames);
collectors_.insert(
std::make_pair(name, gaugeCollector));
}
else if (type == "histogram")
{
auto histogramCollector =
std::make_shared<Collector<Histogram>>(
name, help, labelNames);
collectors_.insert(
std::make_pair(name, histogramCollector));
}
else
{
LOG_ERROR << "Unknown collector type: " << type;
}
}
else
{
LOG_ERROR << "labels must be an array!";
}
}
else
{
LOG_ERROR << "collector must be an object!";
}
}
}
else
{
LOG_ERROR << "collectors must be an array!";
}
}
}
static std::string exportCollector(
const std::shared_ptr<CollectorBase> &collector)
{
auto sampleGroups = collector->collect();
std::string res;
res.append("# HELP ")
.append(collector->name())
.append(" ")
.append(collector->help())
.append("\r\n");
res.append("# TYPE ")
.append(collector->name())
.append(" ")
.append(collector->type())
.append("\r\n");
for (auto const &sampleGroup : sampleGroups)
{
auto const &metricPtr = sampleGroup.metric;
auto const &samples = sampleGroup.samples;
for (auto &sample : samples)
{
res.append(metricPtr->name());
if (!sample.exLabels.empty() || !metricPtr->labels().empty())
{
res.append("{");
for (auto const &label : metricPtr->labels())
{
res.append(label.first)
.append("=\"")
.append(label.second)
.append("\",");
}
for (auto const &label : sample.exLabels)
{
res.append(label.first)
.append("=\"")
.append(label.second)
.append("\",");
}
res.pop_back();
res.append("}");
}
res.append(" ").append(std::to_string(sample.value));
if (sample.timestamp.microSecondsSinceEpoch() > 0)
{
res.append(" ")
.append(std::to_string(
sample.timestamp.microSecondsSinceEpoch() / 1000))
.append("\r\n");
}
else
{
res.append("\r\n");
}
}
}
return res;
}
std::string PromExporter::exportMetrics()
{
std::lock_guard<std::mutex> guard(mutex_);
std::string result;
for (auto const &collector : collectors_)
{
result.append(exportCollector(collector.second));
}
return result;
}
void PromExporter::registerCollector(
const std::shared_ptr<drogon::monitoring::CollectorBase> &collector)
{
std::lock_guard<std::mutex> guard(mutex_);
if (collectors_.find(collector->name()) != collectors_.end())
{
throw std::runtime_error("The collector named " + collector->name() +
" has been registered!");
}
collectors_.insert(std::make_pair(collector->name(), collector));
}
std::shared_ptr<drogon::monitoring::CollectorBase> PromExporter::getCollector(
const std::string &name) const noexcept(false)
{
std::lock_guard<std::mutex> guard(mutex_);
auto iter = collectors_.find(name);
if (iter != collectors_.end())
{
return iter->second;
}
else
{
throw std::runtime_error("Can't find the collector named " + name);
}
}

View File

@ -6,6 +6,7 @@ void TestController::asyncHandleHttpRequest(
std::function<void(const HttpResponsePtr &)> &&callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
// write your application logic here // write your application logic here
counter_->increment();
LOG_WARN << req->matchedPathPatternData(); LOG_WARN << req->matchedPathPatternData();
LOG_DEBUG << "index=" << threadIndex_.getThreadData(); LOG_DEBUG << "index=" << threadIndex_.getThreadData();
++(threadIndex_.getThreadData()); ++(threadIndex_.getThreadData());

View File

@ -1,6 +1,9 @@
#pragma once #pragma once
#include <drogon/HttpSimpleController.h> #include <drogon/HttpSimpleController.h>
#include <drogon/IOThreadStorage.h> #include <drogon/IOThreadStorage.h>
#include <drogon/utils/monitoring/Counter.h>
#include <drogon/utils/monitoring/Collector.h>
#include <drogon/plugins/PromExporter.h>
using namespace drogon; using namespace drogon;
namespace example namespace example
@ -23,9 +26,18 @@ class TestController : public drogon::HttpSimpleController<TestController>
TestController() TestController()
{ {
LOG_DEBUG << "TestController constructor"; LOG_DEBUG << "TestController constructor";
auto collector = std::make_shared<
drogon::monitoring::Collector<drogon::monitoring::Counter>>(
"test_counter",
"The counter for requests to the root url",
std::vector<std::string>());
counter_ = collector->metric(std::vector<std::string>());
collector->registerTo(
*app().getSharedPlugin<drogon::plugin::PromExporter>());
} }
private: private:
drogon::IOThreadStorage<int> threadIndex_; drogon::IOThreadStorage<int> threadIndex_;
std::shared_ptr<drogon::monitoring::Counter> counter_;
}; };
} // namespace example } // namespace example