blob: c058f10dcf1ce95b41130126d2fcd77472bcc6b7 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <deque>
#include <tuple>
#include <atomic>
#include <chrono>
#include <string>
#include <climits>
#include <mutex>
#include "tscore/ink_config.h"
#include "ts/ts.h"
#include <yaml-cpp/yaml.h>
#include "utilities.h"
constexpr auto QUEUE_DELAY_TIME = std::chrono::milliseconds{300}; // Examine the queue every 300ms
using QueueTime = std::chrono::time_point<std::chrono::system_clock>;
enum { RATE_LIMITER_TYPE_SNI = 0, RATE_LIMITER_TYPE_REMAP, RATE_LIMITER_TYPE_MAX };
// order must align with the above
static const char *types[] = {
"sni",
"remap",
};
// no metric for requests we accept; accepted requests should be counted under their usual metrics
enum {
RATE_LIMITER_METRIC_QUEUED = 0,
RATE_LIMITER_METRIC_REJECTED,
RATE_LIMITER_METRIC_EXPIRED,
RATE_LIMITER_METRIC_RESUMED,
RATE_LIMITER_METRIC_MAX
};
// order must align with the above
static const char *suffixes[] = {
"queued",
"rejected",
"expired",
"resumed",
};
static const char *RATE_LIMITER_METRIC_PREFIX = "plugin.rate_limiter";
///////////////////////////////////////////////////////////////////////////////
// Base class for all limiters
//
template <class T> class RateLimiter
{
using QueueItem = std::tuple<T, TSCont, QueueTime>;
using self_type = RateLimiter;
public:
RateLimiter() = default;
RateLimiter(self_type &&) = delete;
self_type &operator=(const self_type &) = delete;
self_type &operator=(self_type &&) = delete;
virtual ~RateLimiter() = default;
virtual bool
parseYaml(const YAML::Node &node)
{
if (node["limit"]) {
_limit = node["limit"].as<uint32_t>();
} else {
// ToDo: Should we require the limit ?
}
const YAML::Node &queue = node["queue"];
// If enabled, we default to UINT32_MAX, but the object default is still 0 (no queue)
if (queue) {
_max_queue = queue["size"] ? queue["size"].as<uint32_t>() : UINT32_MAX;
if (queue["max_age"]) {
_max_age = std::chrono::seconds(queue["max_age"].as<uint32_t>());
}
}
const YAML::Node &metrics = node["metrics"];
if (metrics) {
std::string prefix = metrics["prefix"] ? metrics["prefix"].as<std::string>() : RATE_LIMITER_METRIC_PREFIX;
std::string tag = metrics["tag"] ? metrics["tag"].as<std::string>() : name();
Dbg(dbg_ctl, "Metrics for selector rule: %s(%s, %s)", name().c_str(), prefix.c_str(), tag.c_str());
initializeMetrics(RATE_LIMITER_TYPE_SNI, prefix, tag);
}
return true;
}
void
initializeMetrics(uint type, std::string tag, std::string prefix = RATE_LIMITER_METRIC_PREFIX)
{
TSReleaseAssert(type < RATE_LIMITER_TYPE_MAX);
memset(_metrics, 0, sizeof(_metrics));
std::string metric_prefix = prefix;
metric_prefix.push_back('.');
metric_prefix.append(types[type]);
if (!tag.empty()) {
metric_prefix.push_back('.');
metric_prefix.append(tag);
} else if (!name().empty()) {
metric_prefix.push_back('.');
metric_prefix.append(name());
}
for (int i = 0; i < RATE_LIMITER_METRIC_MAX; i++) {
size_t const metricsz = metric_prefix.length() + strlen(suffixes[i]) + 2; // padding for dot+terminator
char *const metric = static_cast<char *>(TSmalloc(metricsz));
snprintf(metric, metricsz, "%s.%s", metric_prefix.data(), suffixes[i]);
_metrics[i] = TS_ERROR;
if (TSStatFindName(metric, &_metrics[i]) == TS_ERROR) {
_metrics[i] = TSStatCreate(metric, TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_SUM);
}
if (_metrics[i] != TS_ERROR) {
Dbg(dbg_ctl, "established metric '%s' as ID %d", metric, _metrics[i]);
} else {
TSError("failed to create metric '%s'", metric);
}
TSfree(metric);
}
}
// Reserve / release a slot from the active resource limits. Reserve will return
// false if we are unable to reserve a slot.
bool
reserve()
{
std::lock_guard<std::mutex> lock(_active_lock);
TSReleaseAssert(_active <= limit());
if (_active < limit()) {
++_active;
Dbg(dbg_ctl, "Reserving a slot, active entities == %u", _active.load());
return true;
}
return false;
}
void
free()
{
{
std::lock_guard<std::mutex> lock(_active_lock);
--_active;
}
Dbg(dbg_ctl, "Releasing a slot, active entities == %u", _active.load());
}
// Current size of the active_in connections
uint32_t
active() const
{
return _active.load();
}
// Current size of the queue
uint32_t
size() const
{
return _size.load();
}
// Is the queue full (at it's max size)?
bool
full() const
{
return (_size >= max_queue());
}
void
push(T elem, TSCont cont)
{
QueueTime now = std::chrono::system_clock::now();
std::lock_guard<std::mutex> lock(_queue_lock);
_queue.push_front(std::make_tuple(elem, cont, now));
++_size;
}
QueueItem
pop()
{
QueueItem item;
std::lock_guard<std::mutex> lock(_queue_lock);
if (!_queue.empty()) {
item = std::move(_queue.back());
_queue.pop_back();
--_size;
}
return item;
}
void
incrementMetric(uint metric)
{
if (_metrics[metric] != TS_ERROR) {
TSStatIntIncrement(_metrics[metric], 1);
}
}
bool
hasOldEntity(QueueTime now)
{
std::lock_guard<std::mutex> lock(_queue_lock);
if (!_queue.empty()) {
QueueItem item = _queue.back();
std::chrono::milliseconds age = std::chrono::duration_cast<std::chrono::milliseconds>(now - std::get<2>(item));
return (age >= max_age());
}
return false;
}
const std::string &
name() const
{
return _name;
}
uint32_t
limit() const
{
return _limit;
}
uint32_t
max_queue() const
{
return _max_queue;
}
std::chrono::milliseconds
max_age() const
{
return _max_age;
}
void
setName(const std::string &name)
{
_name = name;
}
protected:
std::string _name = "_limiter_"; // The name/descr (e.g. SNI name) of this limiter
uint32_t _limit = UINT32_MAX; // No limit unless specified ...
uint32_t _max_queue = 0; // No queue by default
std::chrono::milliseconds _max_age = std::chrono::milliseconds::zero(); // Max age (ms) in the queue
private:
std::atomic<uint32_t> _active = 0; // Current active number of txns. This has to always stay <= limit above
std::atomic<uint32_t> _size = 0; // Current size of the pending queue of txns. This should aim to be < _max_queue
std::mutex _queue_lock, _active_lock; // Resource locks
std::deque<QueueItem> _queue; // Queue for the pending TXN's. ToDo: Should also move (see below)
int _metrics[RATE_LIMITER_METRIC_MAX];
};