Compare commits

...

5 Commits

Author SHA1 Message Date
Сергей Маринкевич 1897f82b9e qosd: разбил слишком длинную строку 2025-10-08 19:02:03 +07:00
Сергей Маринкевич eff0b26a53 qosd: прокинут kind до BaseNode
Теперь конкретные узлы не сами устанавливают себе `kind_`, а делают это
через `BaseNode` — единую точку входа.
2025-10-08 19:02:03 +07:00
Сергей Маринкевич 43b44d7ee5 qosd: добавлены атрибуты узлов
Мне показалось, будет удобно использовать вложенную структуру в качестве
аргумента конструктора.
2025-10-08 19:02:03 +07:00
Сергей Маринкевич 6a82699c5c qosd: добавлены базовые дисциплины Complex QoS ESR
А именно:

+ [B|P]FIFO;
+ HTB;
+ RED;
+ WRED (a.k.a. `gred` в Linux/TC и нашем CLI);
+ SFQ.

Два замечания:

1. Вместо GRED и RED теперь WRED и RED: RED — как был, так и есть, а
   WRED — специальный узел, который несёт глобальные настройки GRED, но
   параметры RED на VQ задаются через подключение дочерних узлов RED.
2. Параметры RED для SFQ также были оптимизированы: в узле SFQ их не
   будет, но зато к SFQ можно подключить один дочерний узел. Если
   подключить туда RED, то настройки RED будут применяться для per-flow
   RED дисциплины SFQ.
2025-10-08 19:02:03 +07:00
Сергей Маринкевич 63a7907bba qosd: исправлен комментарий 2025-10-08 19:02:03 +07:00
19 changed files with 613 additions and 138 deletions
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <memory>
#include <vector>
#include "ifaces/ILinkMixin.h"
/// \brief Интерфейс для классов-связей между элементами.
/// \tparam TElem Тип элемента, между которыми устанавливается связь.
+3 -2
View File
@@ -3,9 +3,10 @@
#include <stdexcept>
/// \brief Связь для листового узла, не допускающая дочерних элементов.
class LeafLink : public BaseLink {
template <class TElem>
class LeafLink : public BaseLink<TElem> {
public:
using BaseLink::BaseLink;
using BaseLink<TElem>::BaseLink;
void addChild(const NodePtr&) override {
throw std::logic_error("LeafLink cannot have children");
}
+2 -12
View File
@@ -1,17 +1,7 @@
#pragma once
#include "links/BaseLink.h"
/// \brief Связь "один-ко-многим" между элементами одного типа.
/// Каждый дочерний элемент должен быть того же типа, что и родитель.
/// \brief Связь "один-ко-многим" между элементами любого типа.
/// \tparam TElem Тип элемента.
template <class TElem>
class OneToManyLink : public BaseLink<TElem> {
public:
OneToManyLink(std::shared_ptr<TElem> e) : BaseLink<TElem>(e) {}
void addChild(const std::shared_ptr<TElem>& child) override {
/* Each child must be exactly the same type as parent */
if (typeid(*child) != typeid(*this->owner_node_.lock()))
throw std::logic_error("Foundling child");
BaseLink<TElem>::addChild(child);
}
};
using OneToManyLink = BaseLink<TElem>;
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <stdexcept>
/// \brief Обёртка для связей, чтобы сделать их типизированными.
/// Типизированная связь позволяет подключать только узлы заданного типа.
/// \tparam TElem Тип элемента.
/// \tparam TBase Базовый Link. После проверки типа, управление передаётся этому классу.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
/// Если не задан, сравниваем с родителем.
template <class TElem, class TBase, class TExpected = void>
class TypedLink : public TBase {
public:
using ElemPtr = std::shared_ptr<TElem>;
TypedLink(ElemPtr e) : TBase(e) {}
void addChild(const ElemPtr& child) override {
/* Validate type according to policy */
if constexpr (std::is_void_v<TExpected>) {
/* Default behavior: child must be exactly the same type as parent */
if (typeid(*child) != typeid(*this->owner_node_.lock()))
throw std::logic_error("Foundling child");
} else {
/* Explicit expected child type */
if (typeid(*child) != typeid(TExpected))
throw std::logic_error("Unexpected child type");
}
TBase::addChild(child);
}
};
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "links/TypedLink.h"
#include "links/OneToManyLink.h"
#include <type_traits>
#include <typeinfo>
/// \brief Связь "один-ко-многим" между элементами одного типа.
/// Каждый дочерний элемент должен быть того же типа, что и родитель.
/// \tparam TElem Тип элемента.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
template <class TElem, class TExpected = void>
using TypedOneToManyLink = TypedLink<TElem, OneToManyLink<TElem>, TExpected>;
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "links/BaseLink.h"
#include <stdexcept>
/// \brief Связь "один-к-одному" между элементами заданного типа.
/// Позволяет добавить только одного дочернего элемента.
/// \tparam TElem Тип элемента.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
/// Если не задан, сравниваем с родителем.
template <class TElem, class TExpected = void>
using TypedOneToOneLink = TypedLink<TElem, OneToOneLink<TElem>, TExpected>;
+2 -2
View File
@@ -2,7 +2,7 @@
#include <iostream>
#include "mixins/LazyLinkMixin.h"
#include <memory>
#include "links/OneToManyLink.h"
#include "links/TypedOneToManyLink.h"
#include "links/OneToOneLink.h"
#include "Logger.h"
@@ -34,7 +34,7 @@ protected:
if (typeid(*child) == typeid(*this)) {
Logger::get("Mixin").dbg("--- Mutate to OneToMany");
newLink = std::make_shared<OneToManyLink<TElem>>(*this);
newLink = std::make_shared<TypedOneToManyLink<TElem>>(*this);
} else {
Logger::get("Mixin").dbg("--- Mutate to OneToOne");
newLink = std::make_shared<OneToOneLink<TElem>>(*this);
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины BFIFO. Лист.
class BFIFONode : public LeafNode,
public FabricMixin<BFIFONode> {
public:
struct Config {
/// Размер очереди в байтах.
std::uint64_t limit = 0;
};
~BFIFONode() {
Logger::get("ConDes").dbg(std::string("--- BFIFO destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<BFIFONode>;
BFIFONode(std::string&& name) : LeafNode(std::move(name), "BFIFO") {
Logger::get("ConDes").dbg(std::string("--- BFIFO constructor called for: ") + name_);
}
BFIFONode(std::string&& name, Config&& config) : LeafNode(std::move(name), "BFIFO"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- BFIFO constructor called for: ") + name_);
}
Config config_{};
};
+5 -1
View File
@@ -7,9 +7,13 @@
/// Содержит имя и тип узла, реализует интерфейс INode.
class BaseNode : public virtual INode {
public:
BaseNode(std::string name) : name_(std::move(name)) {
/// \brief Конструктор узла.
/// \param name Неуникальное имя узла.
BaseNode(std::string name, std::string kind) :
name_(std::move(name)), kind_(std::move(kind)) {
Logger::get("ConDes").dbg(std::string("--- Base constructor called for: ") + name_);
}
/// \brief Неуникальное имя узла.
const std::string& name() const override { return name_; }
const std::string& kind() const override { return kind_; }
~BaseNode() {
+6 -6
View File
@@ -6,17 +6,17 @@
#include "Logger.h"
/// \brief Класс сложного (составного) узла дерева.
/// Может содержать несколько дочерних ComplexNode и один SimpleNode.
/// Может содержать несколько дочерних узлов такого же типа или один узел
/// отличного типа.
class ComplexNode : public BaseNode,
virtual public HierarchicalLinkMixin<INode>,
public FabricMixin<ComplexNode> {
virtual public HierarchicalLinkMixin<INode> {
public:
~ComplexNode() {
Logger::get("ConDes").dbg(std::string("--- Complex destructor called for: ") + name_);
}
private:
friend class FabricMixin<ComplexNode>;
ComplexNode(std::string name) : BaseNode(std::move(name)) {
protected:
ComplexNode(std::string name, std::string kind) :
BaseNode(std::move(name), std::move(kind)) {
Logger::get("ConDes").dbg(std::string("--- Complex constructor called for: ") + name_);
}
};
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <cstdint>
#include <string>
#include "nodes/ComplexNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины HTB. Составной, допускает нескольких детей.
class HTBNode : public ComplexNode,
public FabricMixin<HTBNode> {
public:
struct Config {
std::uint64_t cir = 0; ///< Разрешённая полоса, CIR, бит/с.
std::uint64_t cburst = 0; ///< Токены для CIR, байты.
std::uint64_t pir = 0; ///< Допустимая полоса, PIR, бит/с.
std::uint64_t pburst = 0; ///< Токены для PIR, байты.
std::uint32_t prio = 0; ///< Приоритет класса.
std::uint32_t quantum = 0; ///< Квант DRR, байты.
/// \brief Поправка размера пакета, байты.
///
/// \note Вообще, этот параметр исторически взят с Linux/TC HTB.
/// И в таком контексте: речь **не** про STAB, а именно про
/// параметр класса HTB. Он, в отличие от STAB, не влияет на сам
/// размер пакета, а используется только для поправки шейпера
/// конкретно этого класса. Впрочем, в BC2 есть такая же местная
/// поправка для шейперов, см. FS 31.5.2.3 Packet Length Offset.
std::int32_t overhead = 0;
};
~HTBNode() {
Logger::get("ConDes").dbg(std::string("--- HTB destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<HTBNode>;
HTBNode(std::string&& name) : ComplexNode(std::move(name), "HTB") {
Logger::get("ConDes").dbg(std::string("--- HTB constructor called for: ") + name_);
}
HTBNode(std::string&& name, Config&& config) : ComplexNode(std::move(name), "HTB"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- HTB constructor called for: ") + name_);
}
Config config_{};
};
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/LeafLink.h"
#include "Logger.h"
/// \brief Базовый класс для краевых (листовых) узлов дисциплин.
/// Не допускает дочерних элементов.
class LeafNode : public BaseNode,
virtual public LazyLinkMixin<LeafLink<INode>> {
public:
~LeafNode() {
Logger::get("ConDes").dbg(
std::string("--- Leaf destructor called for: ") + name_
);
}
protected:
LeafNode(std::string name, std::string kind) :
BaseNode(std::move(name), std::move(kind)) {
Logger::get("ConDes").dbg(
std::string("--- Leaf constructor called for: ") +
name_ + ", kind=" + kind_
);
}
};
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины PFIFO. Лист.
class PFIFONode : public LeafNode,
public FabricMixin<PFIFONode> {
public:
struct Config {
/// \brief Кол-во пакетов.
std::uint32_t limit;
};
~PFIFONode() {
Logger::get("ConDes").dbg(std::string("--- PFIFO destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<PFIFONode>;
PFIFONode(std::string&& name) : LeafNode(std::move(name), "PFIFO") {
Logger::get("ConDes").dbg(std::string("--- PFIFO constructor called for: ") + name_);
}
PFIFONode(std::string&& name, Config&& config) : LeafNode(std::move(name), "PFIFO"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- PFIFO constructor called for: ") + name_);
}
Config config_{};
};
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины RED. Лист; не допускает дочерних элементов.
///
/// \note Может быть подключен к:
/// + WRED для настройки VQ;
/// + SFQ для настройки per-flow RED.
class REDNode : public LeafNode,
public FabricMixin<REDNode> {
public:
struct Config {
std::uint64_t limit = 0; ///< Размер буфера, байты (обязателен).
std::uint64_t min = 0; ///< Нижняя граница для разметки, байты.
std::uint64_t max = 0; ///< Верхняя граница для разметки, байты.
std::uint32_t avpkt = 0; ///< Средний размер пакета, байты (обязателен).
std::uint64_t burst = 0; ///< Всплеск, байты.
double probability = 0.0; ///< Максимальная вероятность, 0..1.
std::uint64_t bandwidth = 0; ///< Скорость интерфейса, бит/с.
bool ecn = false; ///< Включить ECN-разметку вместо дропа.
bool harddrop = false; ///< Жёсткий дроп при превышении max.
bool nodrop = false; ///< Не дропать non-ECN-пакеты.
};
~REDNode() {
Logger::get("ConDes").dbg(std::string("--- RED destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<REDNode>;
REDNode(std::string&& name)
: LeafNode(std::move(name), "RED") {
Logger::get("ConDes").dbg(std::string("--- RED constructor called for: ") + name_);
}
REDNode(std::string&& name, Config&& config)
: LeafNode(std::move(name), "RED"), config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- RED constructor called for: ") + name_);
}
Config config_{};
};
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/TypedOneToOneLink.h"
#include "nodes/REDNode.h"
#include "Logger.h"
/// \brief Узел дисциплины SFQ. Допускает одного ребёнка типа REDNode для настройки per-flow RED.
class SFQNode : public BaseNode,
virtual public LazyLinkMixin<TypedOneToOneLink<INode, REDNode>>,
public FabricMixin<SFQNode> {
public:
/// \note Параметры RED per-flow задаются через дочерний REDNode.
struct Config {
std::uint32_t limit = 0; ///< Суммарный лимит по всем потокам, пакеты
std::uint32_t depth = 0; ///< Лимит на один поток, пакеты
std::uint32_t divisor = 1024; ///< Размер хэш-таблицы (степень двойки)
std::uint32_t perturb = 0; ///< Период пертурбации, секунды
std::uint32_t flows = 0; ///< Число потоков
std::uint32_t quantum = 0; ///< Порция RR, байты
bool headdrop = false; ///< Отбрасывать из головы очереди
};
~SFQNode() {
Logger::get("ConDes").dbg(std::string("--- SFQ destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<SFQNode>;
SFQNode(std::string&& name) : BaseNode(std::move(name), "SFQ") {
Logger::get("ConDes").dbg(std::string("--- SFQ constructor called for: ") + name_);
}
SFQNode(std::string&& name, Config&& config) : BaseNode(std::move(name), "SFQ"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- SFQ constructor called for: ") + name_);
}
Config config_{};
};
+2 -4
View File
@@ -9,14 +9,12 @@
/// \brief Класс простого (листового) узла дерева.
/// Может содержать только одного дочернего ComplexNode.
class SimpleNode : public BaseNode,
virtual public LazyLinkMixin<OneToOneLink<INode>>,
public FabricMixin<SimpleNode> {
virtual public LazyLinkMixin<OneToOneLink<INode>>{
public:
~SimpleNode() {
Logger::get("ConDes").dbg(std::string("--- Simple destructor called for: ") + name_);
}
private:
friend class FabricMixin<SimpleNode>;
protected:
SimpleNode(std::string name) : BaseNode(std::move(name)) {
Logger::get("ConDes").dbg(std::string("--- Simple constructor called for: ") + name_);
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <string>
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/TypedOneToManyLink.h"
#include "nodes/REDNode.h"
#include "Logger.h"
class WREDNode : public BaseNode,
virtual public LazyLinkMixin<TypedOneToManyLink<INode, REDNode>>,
public FabricMixin<WREDNode> {
public:
struct Config {
bool grio = false; ///< Наследование буферов (GRIO)
std::uint64_t limit = 0; ///< Глобальный лимит буфера, байты (необязателен)
bool ecn = false; ///< Включить ECN на дисциплине
bool harddrop = false; ///< Жёсткий дроп при превышении max
};
~WREDNode() {
Logger::get("ConDes").dbg(std::string("--- WRED destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<WREDNode>;
WREDNode(std::string&& name) : BaseNode(std::move(name), "WRED") {
Logger::get("ConDes").dbg(std::string("--- WRED constructor called for: ") + name_);
}
WREDNode(std::string&& name, Config&& config) : BaseNode(std::move(name), "WRED"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- WRED constructor called for: ") + name_);
}
Config config_{};
};
+116
View File
@@ -0,0 +1,116 @@
# Классы внутреннего представления
1. [[#HTB]];
2. [[#FIFO]] (хотя бы одну);
3. [[#RED]];
4. [[#GRED]];
5. [[#SFQ]].
## HTB
Иерархичный TBF. Имеет классы, к которым могут подключаться другие дисциплины. Т.е. к ней могут быть подключены несколько других узлов.
Из дисциплины:
+ `default` — Минорная часть ID класса по умолчанию. Dunno, надо ли нам такое вообще. Я об этом не думал. Сейчас у нас всё на фильтры `u32` завязано.
+ `r2q` — Кванты DRR (computed as rate in Bps/r2q). КМК, нам это не надо, мы этим никогда не пользовались. Единственное: мы раньше не пользовались DRR, а сейчас я хочу на этом построить Basic QoS. — **UPD:** Это можно в глобальной конфигурации задать.
+ `debug` — Какой-то "string of 16 numbers each 0-3", в душе не знаю, что это.
+ `direct_qlen` — Лимит прямой очереди в штуках пакетов. Мы этим вряд ли воспользуемся, т.к. у нас нет такого сценария, что какой-то пакет нужной пропускать мимо QoS. Ну, я таких сценариев не знаю. — **UPD:** В целом, можно вписать в мою архитектуру, если явно отобразить такой узел в дереве: будет класс, который матчит весь трафик (как в Complex QoS `class-default`), и в качестве дочернего узла у него некий DirectNode.
+ `offload` — Включение аппаратного QoS. У нас сейчас точно нигде поддержки нет. Вроде бы в новом SDK появилась какая-то поддержка на каких-то платформах Marvell. Для абстрактного дерева эта опция не подходит.
Из классов:
+ `rate` — Разрешённая полоса (можно заимствовать выше этой полосы). (обязательный)
+ `burst` — Кол-во токенов (байтов) TBF для этого класса.
+ `mpu` — "minimum packet size used in rate computations" — Мне кажется, что этот параметр вообще давно не работает: RTAB'ы померли, `iproute2` заполняет их только для обратной совместимости (актуальные ядра сразу делают на них `qdisc_put_rtab()`).
+ `overhead` — Поправка размера пакета для вычислений. Не путать со STAB'ом: здесь это атрибут класса HTB, и он не влияет размер пакета за пределами класса. По сути, это поправка к введённым `rate` и `ceil`.
+ `linklay` — Протокол передачи. Кроме Ethenet по умолчанию знаю только "e.g. atm". Бесполезно.
+ `ceil` — Допустимая полоса (заимствовать нельзя).
+ `cburst` — Аналог `burst` для `ceil`.
+ `mtu` — В дисциплине TBF это что-то вроде микро-burst, используемы для `peakrate`. Но здесь это такая же бесполезная вещь, как и `mpu`. По тем же причинам.
+ `prio` — Приоритет. Определённо надо.
+ `quantum` — Позволяет настроить *жадность* опустошения очередей в процессе DRR. По дефолту `r2q`. В целом, наверно, надо. Сейчас мы это не настраиваем, но, как я писал выше, для Basic QoS может пригодиться.
У этой дисциплины может быть лишь одна дочерняя дисциплина.
## FIFO
FIFO как настоящая очередь может быть только краевым узлом, то есть не имеет дочерних дисциплин и классов.
### PFIFO
Очередь с ограничением в штуках пакетов. Дисциплина по умолчанию для классов HTB. Параметры дисциплины:
+ `limit` — Размер очереди, кол-во пакетов (по умолчанию[^1] — `qlen` с интерфейса).
### BFIFO
Очередь с ограничением в байтах. Дисциплина по умолчанию для TBF. Параметры дисциплины:
+ `limit` — Размер очереди, байты (по умолчанию[^1] — `limit` с TBF).
[^1]: Вообще, для FIFO по умолчанию параметр `limit` равен `qlen` или `qlen * MTU`. Т.е., HTB вообще никакой параметр туда не передаёт, а TBF передаёт свой же `limit`, который проецирует на дочернюю дисциплину, если это FIFO (даже если руками новую FIFO создать).
## RED
Random Early Detection.
Может быть только краевым узлом.
+ `limit` — Размер буфера, байты. (обязательный, используем)
+ `min`, `max` — Границы для разметки пакетов: до минимума вообще не метим пакеты; после максимума вероятность метки максимальная. Заполненность очереди, байты. (используем)
+ `avpkt` — Средний размер пакета. Байты. Используется для каких-то хитрых вычислений. (обязательный, используем)
+ `burst` — Всплеск. Смысл такой же, как и везде, но как именно он работает — мне неясно. Факт в том, что чем выше `burst`, тем дольше до RED доходит, что нужно маркировать трафик. Байты. (используем)
+ `adaptive` — Какая-то умная шняга для автоматической подгонки параметров под текущее поведение трафика. Мы это не используем, кстати. Гля [[adaptiveRed.pdf]].
+ `probability` — Максимальная вероятность *помаркать* пакет. Число с плавающей точкой, континуум 0-1. (используем)
+ `bandwidth` — Не про шейпинг, но про скорость интерфейса, используется для расчёта средней длины очереди. Биты в секунду.
+ `ecn` — Включить именно маркировку пакетов, вместо отбрасывания, для сдерживания полосы потока. Пакет всё же будет отброшен, если длина очереди для этого потока превысит `limit`. Рекомендуемо, но мы этим не пользуемся.
+ `harddrop` — С этим параметром пакет будет отброшен, если длина очереди превысит `max`.
+ `nodrop` — С этим параметром дисциплина не будет отбрасывать пакеты, которые не может маркировать ("not ECN-capable"). По умолчанию такие пакеты дропаются.
Параметры, связанные с QEVENTS (`man 8 tc`), рассматривать точно не будем.
## GRED
А это, типа, "Generalized RED". Что бы это не значило. Мы это как WRED используем. Литературы по нему мало.
Тут вводится понятие "виртуальной очереди" (VQ). И к самому Linux TC оно отношения не имеет. Оно даже к очередям отношение имеет опосредованное: никакой очереди фактически не выделяется, поэтому она и *виртуальная*. И настройка этих очередей довольно виктимная: нужно послать `CHANGE` на уже созданную дисциплину и в качестве параметра передать номер очереди, настройки которой мы хотим изменить.
В этом проекте присутствует как узел WRED. Узел WRED несёт в себе только глобальные настройки дисциплины. Для конфигурирования VQ к узлу WRED прикрепляются узлы RED.
+ `vqs` — Кол-во виртуальных очередей. (обязательно) — **UPD:** Вообще, нам это не нужно: мы и так можем посчитать, сколько VQ мы определили, по самим дочерним узлам.
+ `default` — Виртуальная очередь по умолчанию. (обязательно) — **UPD:** Это тоже не нужно, т.к. мы фильтрами на самой очереди скажем, что она дефолтная.
+ `grio` — Вкл. наследование буферов. Условно, приоритетный трафик затрагивает, в том числе, счётчики менее приоритетного трафика.
+ `limit` — Размер буфера, байты. Здесь, в отличие от RED и VQ, является необязательным. Ограничивает очередь *мимо* VQ, а также задаёт верхнюю планку для каждой VQ (больше этого `limit` нельзя задавать `limit` для VQ).
+ `ecn` — Возможность включить маркировку пакетов на всю дисциплину, либо включение только для отдельной VQ. Вроде бы, и там, и тут включить нельзя. Кроме того, на 4.14, например, эти флаги на VQ вообще не используются.
+ `harddrop` — С этим параметром пакет будет отброшен, если длина очереди превысит `max`. Аналогично `ecn`.
Ну и далее для VQ аналогично RED:
+ `limit` — Размер буфера, байты. (обязательный, используем)
+ `min`, `max` — Границы для разметки пакетов: до минимума вообще не метим пакеты; после максимума вероятность метки максимальная. Заполненность очереди, байты. (используем)
+ `avpkt` — Средний размер пакета. Байты. Используется для каких-то хитрых вычислений. (обязательный, используем)
+ `burst` — Всплеск. Смысл такой же, как и везде, но как именно он работает — мне неясно. Факт в том, что чем выше `burst`, тем дольше до RED доходит, что нужно маркировать трафик. Байты. (используем)
+ `probability` — Максимальная вероятность *помаркать* пакет. Число с плавающей точкой, континуум 0-1. (используем)
+ `bandwidth` — Не про шейпинг, но про скорость интерфейса, используется для расчёта средней длины очереди. Биты в секунду.
## SFQ
Также может быть только краевым узлом.
Дисциплина работает по принципу "отобрать и поделить". Т.е. поток трафика на исходящем интерфейсе распределяется по множеству динамически создаваемых очередей на основании хэша пакета. Эти динамические очереди обслуживаются условным RR. За счёт такого подхода, гипотетически, никакая сессия не может монополизировать линк. Грубо говоря, социалистическая революция в рамках дисциплины на интерфейсе.
Несмотря на то, что у этой дисциплины при создании нет обязательных параметров, этих параметров всё же очень много:
+ `limit` — Кол-во пакетов, задерживаемое дисциплиной. Это суммарное ограничение по всем потокам.
+ `depth` — Кол-во пакетов, задерживаемое одним потоком.
+ `divisor` — Размер хэш-таблицы. Чем больше потоков дозволено, тем медленнее будет происходить поиск по ним, но увеличение размера таблицы сокращает время поиска. Должно быть степенью двойки, по умолчанию 1024.
+ `perturb` — Кол-во секунд для периода пертурбации, т.е. как часто изменять хэш-функцию (за счёт соли).
+ `flows` — Кол-во потоков. (единственное, что мы настраиваем в CLI; хотя `brasd` SFQ тоже пользуется и настраивает больше параметров!)
+ `quantum` — Порция трафика для одного раунда RR-процесса. Байты.
+ `headdrop` — Флаг для отбрасывания трафика из головы очереди, а не с хвоста. Гипотетически, так отклик TCP лучше.
+ `redflowlimit` — Ограничение в байтах на очередь **потока**. Заодно включает сам RED на каждом потоке SFQ. Да, но мы этим тоже не пользуемся.
+ Параметры RED третий раз перечислять не очень-то хочется. Но тут поддерживаются все параметры из [[#RED]], кроме `adaptive`.
Параметры RED на SFQ конфигурируются аналогично WRED и VQ: к SFQ подключается RED, с которого и будет считываться конфигурация per-flow RED.
+134 -110
View File
@@ -2,8 +2,12 @@
#include "Logger.h"
#include "nodes/SimpleNode.h"
#include "nodes/ComplexNode.h"
#include "nodes/HTBNode.h"
#include "nodes/PFIFONode.h"
#include "nodes/BFIFONode.h"
#include "nodes/REDNode.h"
#include "nodes/WREDNode.h"
#include "nodes/SFQNode.h"
#include "iterators/Traversal.h"
@@ -27,136 +31,156 @@ void printTreeLadder(INode& node) {
}
int main() {
Logger::setMinSeverity("MAIN", Logger::Severity::Debug);
Logger::setMinSeverity("MAIN", Logger::Severity::Warning);
Logger::suppressCategory("Node");
Logger::suppressCategory("Link");
Logger::suppressCategory("Mixin");
Logger::suppressCategory("ConDes");
Logger::setMinSeverity("ConDes", Logger::Severity::Info);
Logger::setMinSeverity("ConDes", Logger::Severity::Warning);
auto& logger = Logger::get("MAIN");
logger.info("Entering main scope...");
int negative_fails_count = 0;
{
auto root = ComplexNode::create("ComplexRoot");
auto child1 = ComplexNode::create("ComplexChild1");
// Строим дерево HTB с несколькими уровнями и разными краевыми дисциплинами
auto root = HTBNode::create("HTB_root");
root->linkChild(child1);
auto htbA = HTBNode::create("HTB_A");
auto htbB = HTBNode::create("HTB_B");
root->linkChild(htbA);
root->linkChild(htbB);
std::cout << "\nInit tree:\n";
// Ветка A: PFIFO, RED, BFIFO на краях
auto htbA1 = HTBNode::create("HTB_A1");
auto htbA2 = HTBNode::create("HTB_A2");
auto htbA3 = HTBNode::create("HTB_A3");
htbA->linkChild(htbA1);
htbA->linkChild(htbA2);
htbA->linkChild(htbA3);
auto pfifoLeaf = PFIFONode::create("PFIFO_leaf");
htbA1->linkChild(pfifoLeaf);
htbA2->linkChild(REDNode::create("RED_leaf"));
htbA3->linkChild(BFIFONode::create("BFIFO_leaf"));
// Ветка B: WRED (с RED детьми) и SFQ (с одним RED)
auto htbB1 = HTBNode::create("HTB_B1");
auto htbB2 = HTBNode::create("HTB_B2");
htbB->linkChild(htbB1);
htbB->linkChild(htbB2);
// WRED как лист с несколькими RED
auto wred = WREDNode::create("WRED_leaf");
wred->linkChild(REDNode::create("RED_vq1"));
wred->linkChild(REDNode::create("RED_vq2"));
htbB1->linkChild(wred);
// SFQ с одним RED
auto sfq = SFQNode::create("SFQ_leaf");
htbB2->linkChild(sfq);
// Негативный сценарий: SFQ должен дозволять подключать к себе
// только узлы RED.
logger.info("[Negative] Попытка добавить не-RED в SFQ (должно упасть)");
try {
sfq->linkChild(HTBNode::create("nonRED_on_SFQ"));
logger.err("Удалось подключить HTB к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
// И всё-таки подключим SFQ
sfq->linkChild(REDNode::create("RED_on_SFQ"));
}
// Другие негативные сценарии
logger.info("[Negative] Попытка подключить не-RED к SFQ (должно упасть)");
try {
pfifoLeaf->linkChild(HTBNode::create("ShouldFailOnSFQ"));
logger.err("Удалось подключить ребёнка к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить что-то к PFIFO (должно упасть)");
try {
pfifoLeaf->linkChild(REDNode::create("ShouldFailOnPFIFO"));
logger.err("Удалось подключить ребёнка к PFIFO!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить второй не-HTB узел к HTB_A1 (должно упасть)");
try {
htbA1->linkChild(BFIFONode::create("SecondLeafOnHTB_A1"));
logger.err("Удалось подключить второй лист к HTB_A1!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить не-HTB узел к HTB_A (должно упасть)");
try {
htbA->linkChild(REDNode::create("AnotherLeafOnHTB_A"));
logger.err("Удалось подключить второй лист к HTB_A1!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка добавить не-RED в WRED (должно упасть)");
try {
wred->linkChild(PFIFONode::create("NotRED_for_WRED"));
logger.err("Удалось подключить не-RED к WRED!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка добавить второго ребёнка в SFQ (должно упасть)");
try {
sfq->linkChild(REDNode::create("SecondRED_on_SFQ"));
logger.err("Удалось подключить второй ребёнок к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка RED->RED (должно упасть, RED — лист)");
try {
auto redLeaf = REDNode::create("RED_alone");
redLeaf->linkChild(REDNode::create("NestedRED"));
logger.err("Удалось подключить RED к RED!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
// Покрасуемся получившимся деревом:
std::cout << "\nHTB tree:\n";
printTreeLadder(*root);
std::cout << "\n";
{
auto child2 = ComplexNode::create("ComplexChild2");
root->linkChild(child2);
auto subchild2 = SimpleNode::create("SimpleSubChild2");
child2->linkChild(subchild2);
auto child3 = ComplexNode::create("ComplexChild3");
root->linkChild(child3);
child3->linkChild(SimpleNode::create("SimpleSubChild3"));
{
// Негативный сценарий 1: попытка добавить второй SimpleNode к ComplexNode
try {
child2->linkChild(SimpleNode::create("ShouldFail"));
logger.err("[ERROR] Не должно было получиться добавить второй SimpleNode к ComplexNode!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
// Негативный сценарий 2: попытка добавить ComplexNode к SimpleNode
// Это допустимо: SimpleNode может иметь ComplexNode в качестве единственного ребёнка
subchild2->linkChild(ComplexNode::create("GoodComplex"));
logger.info("[OK] ComplexNode успешно добавлен к SimpleNode как единственный ребёнок.");
// Негативный сценарий: попытка добавить второго ребёнка к SimpleNode
try {
subchild2->linkChild(SimpleNode::create("ShouldFail2"));
logger.err("[ERROR] Не должно было получиться добавить второго ребёнка к SimpleNode!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
// Негативный сценарий: попытка добавить SimpleNode в ComplexNode, который уже содержит несколько ComplexNode-дочерних
try {
root->linkChild(SimpleNode::create("BadSimple"));
logger.err("[ERROR] Не должно было получиться добавить SimpleNode в ComplexNode с несколькими ComplexNode-дочерними!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
}
std::cout << "\nAnother tree:\n";
printTreeLadder(*root);
std::cout << "\n";
std::cout << "\nList:\n";
printTreeList(*root);
std::cout << "\n";
std::cout << "\nBFS:\n";
printTreeBFS(*root);
std::cout << "\n";
logger.info("Unlinking ComplexChild2...\n");
child2->unlinkParent();
std::cout << "\nTree after unlink:\n";
printTreeLadder(*root);
std::cout << "\n";
logger.info("Put refs of ComplexChild2, ComplexChild3 and its children");
}
std::cout << "\nTree after scope out:\n";
printTreeLadder(*root);
// Обход в глубину:
std::cout << "\nDFS list:\n";
printTreeList(*root);
std::cout << "\n";
logger.info("Unlinking ComplexChild1 and ComplexChild3...\n");
child1->unlinkParent();
for (auto child : traversal::BFS(root)) {
if (child->name() == "ComplexChild3") {
/* Avoid of disconnecting a node of the tree
* we're traversing. Method holds reference
* to it's object itself. But iterator ignores
* smart pointers, feeling free to dereference
* the destroyed object.
*/
#if 1
/* Keep in mind to keep your reference until
* the cycle ends, if you want more than just
* remove subtree.
*/
NodePtr node = *child;
node->unlinkParent();
std::cout << "Unlinked: "
<< node->name() << std::endl;
break;
#else
/* Otherwise, just break immediately you
* unlinked a node. The current pointer is not
* a valid pointer anymore.
*/
child->unlinkParent();
break;
#endif
}
}
root->linkChild(SimpleNode::create("SimpleChild4"));
std::cout << "\nTree flush and link SimpleChild4:\n";
printTreeLadder(*root);
// Обход в ширину:
std::cout << "\nBFS list:\n";
printTreeBFS(*root);
std::cout << "\n";
}
logger.info("Exited main scope. All smart pointers destroyed.");
logger.info("(It don't? Check ConDes logger)");
if (negative_fails_count)
logger.err(std::string("Negative fails: ") + std::to_string(negative_fails_count));
return 0;
}