├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── common ├── CMakeLists.txt ├── distributions │ ├── CMakeLists.txt │ ├── constant_distribution.cpp │ ├── constant_distribution.h │ ├── distribution.cpp │ ├── distribution.h │ ├── distribution_factory.cpp │ ├── distribution_factory.h │ ├── exponential_distribution.cpp │ ├── exponential_distribution.h │ ├── normal_distribution.cpp │ ├── normal_distribution.h │ ├── uniform_distribution.cpp │ └── uniform_distribution.h ├── macros.h ├── tsc_clock.cpp ├── tsc_clock.h ├── utils.cpp └── utils.h ├── scheduler ├── CMakeLists.txt ├── benchmark │ ├── CMakeLists.txt │ ├── packet.h │ ├── pktgen │ │ ├── CMakeLists.txt │ │ └── pktgen.cpp │ ├── scripts │ │ └── plot_results.py │ └── server │ │ ├── CMakeLists.txt │ │ ├── policies │ │ ├── CMakeLists.txt │ │ ├── policy_fcfs.cpp │ │ ├── policy_fcfs.h │ │ ├── policy_wsjf_dropmax.cpp │ │ ├── policy_wsjf_dropmax.h │ │ ├── policy_wsjf_droptail.cpp │ │ ├── policy_wsjf_droptail.h │ │ ├── policy_wsjf_hffs.cpp │ │ ├── policy_wsjf_hffs.h │ │ └── scheduler.hpp │ │ └── server.cpp ├── cmake │ └── Finddpdk.cmake └── heaps │ ├── binomial_heap.hpp │ ├── bounded_heap.hpp │ ├── fcfs_queue.hpp │ ├── fibonacci_heap.hpp │ ├── hffs_queue │ ├── hardware │ │ ├── ffs.sv │ │ ├── heap_ops_pkg.sv │ │ ├── pipelined_heap.sv │ │ └── pipelined_heap_wrapper.sv │ └── software │ │ └── hffs_queue.hpp │ └── priority_queue.hpp └── simulator ├── CMakeLists.txt ├── configs ├── examples │ └── example_1.cfg └── templates │ ├── full_matcher.cfg │ ├── iid_job_sizes.cfg │ └── tcp_reassembly.cfg ├── scripts ├── __init__.py ├── adversary │ ├── __init__.py │ ├── analyze_common.py │ ├── analyze_fcfs.py │ ├── analyze_fq.py │ ├── analyze_sjf.py │ ├── analyze_sjf_inorder.py │ ├── analyze_wsjf.py │ └── analyze_wsjf_inorder.py ├── common.py ├── generate_jobs.py └── plot_results.py ├── src ├── CMakeLists.txt ├── applications │ ├── CMakeLists.txt │ ├── application.cpp │ ├── application.h │ ├── application_factory.cpp │ ├── application_factory.h │ ├── echo.cpp │ ├── echo.h │ ├── iid_job_sizes.cpp │ ├── iid_job_sizes.h │ ├── tcp_reassembly.cpp │ └── tcp_reassembly.h ├── packet │ ├── CMakeLists.txt │ ├── packet.cpp │ └── packet.h ├── queueing │ ├── CMakeLists.txt │ ├── base_queue.h │ ├── fcfs_queue.cpp │ ├── fcfs_queue.h │ ├── fq_queue.cpp │ ├── fq_queue.h │ ├── queue_factory.cpp │ ├── queue_factory.h │ ├── sjf_inorder_queue.cpp │ ├── sjf_inorder_queue.h │ ├── sjf_queue.cpp │ ├── sjf_queue.h │ ├── wsjf_inorder_queue.cpp │ ├── wsjf_inorder_queue.h │ ├── wsjf_queue.cpp │ └── wsjf_queue.h ├── server │ ├── CMakeLists.txt │ ├── server.cpp │ └── server.h ├── simulator.cpp ├── simulator.h └── traffic │ ├── CMakeLists.txt │ ├── synthetic_trafficgen.cpp │ ├── synthetic_trafficgen.h │ ├── trace_trafficgen.cpp │ ├── trace_trafficgen.h │ ├── trafficgen.cpp │ ├── trafficgen.h │ ├── trafficgen_factory.cpp │ └── trafficgen_factory.h └── traces └── full_matcher.csv /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | .vscode/ 4 | .vscode-ctags 5 | build/ 6 | data/ 7 | tags 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(SurgeProtector) 3 | 4 | # Output targets 5 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 6 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 7 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 8 | 9 | # Boost 10 | find_package(Boost REQUIRED COMPONENTS program_options) 11 | link_libraries(Boost::program_options) 12 | 13 | # Libconfig 14 | find_library(LIBCONFIG config) 15 | find_library(LIBCONFIGPP config++) 16 | 17 | if(NOT LIBCONFIG OR NOT LIBCONFIGPP) 18 | message(FATAL_ERROR "libconfig not found") 19 | else() 20 | message(STATUS "Found libconfig: " ${LIBCONFIG}) 21 | message(STATUS "Found libconfig++: " ${LIBCONFIGPP}) 22 | endif() 23 | 24 | link_libraries(${LIBCONFIG}) 25 | link_libraries(${LIBCONFIGPP}) 26 | 27 | # C++ STD version 28 | set(CMAKE_CXX_STANDARD 17) 29 | set(CMAKE_CXX_STANDARD_REQUIRED True) 30 | 31 | # C STD version 32 | set(CMAKE_C_STANDARD 99) 33 | set(CMAKE_C_STANDARD_REQUIRED True) 34 | set(CMAKE_C_EXTENSIONS True) 35 | 36 | # CXX flags 37 | add_compile_options(-m64 -O3 -march=native -g 38 | -pedantic-errors -Wall -Wextra -Werror) 39 | 40 | # Headers 41 | include_directories(.) 42 | 43 | # Sources 44 | add_subdirectory(common) 45 | add_subdirectory(scheduler) 46 | add_subdirectory(simulator) 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Clear BSD License 2 | 3 | Copyright (c) 2022 Carnegie Mellon University 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted (subject to the limitations in the disclaimer 8 | below) provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from this 19 | software without specific prior written permission. 20 | 21 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY 22 | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 23 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 30 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(common STATIC 4 | tsc_clock.cpp 5 | utils.cpp 6 | ) 7 | 8 | # Sources 9 | add_subdirectory(distributions) 10 | -------------------------------------------------------------------------------- /common/distributions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(distributions STATIC 4 | constant_distribution.cpp 5 | distribution.cpp 6 | distribution_factory.cpp 7 | exponential_distribution.cpp 8 | normal_distribution.cpp 9 | uniform_distribution.cpp 10 | ) 11 | -------------------------------------------------------------------------------- /common/distributions/constant_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "constant_distribution.h" 2 | 3 | // STD headers 4 | #include 5 | #include 6 | 7 | /** 8 | * ConstantDistribution implementation. 9 | */ 10 | void ConstantDistribution::printConfiguration() const { 11 | std::cout << "{ type: " << name() << ", " 12 | << std::fixed << std::setprecision(2) 13 | << "value: " << kSampleStats.getMean() << " }"; 14 | } 15 | -------------------------------------------------------------------------------- /common/distributions/constant_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_CONSTANT_DISTRIBUTION_H 2 | #define COMMON_DISTRIBUTIONS_CONSTANT_DISTRIBUTION_H 3 | 4 | // Library headers 5 | #include "distribution.h" 6 | 7 | /** 8 | * Represents a constant distribution. 9 | */ 10 | class ConstantDistribution : public Distribution { 11 | public: 12 | explicit ConstantDistribution(const double mean) : 13 | Distribution(name(), mean, mean) { 14 | kSampleStats.set(mean, 0); 15 | } 16 | virtual ~ConstantDistribution() {} 17 | 18 | /** 19 | * Print the distribution configuration. 20 | */ 21 | virtual void printConfiguration() const override; 22 | 23 | /** 24 | * Sample from the distribution. 25 | */ 26 | double sample() override { return kSampleStats.getMean(); } 27 | 28 | /** 29 | * Distribution name. 30 | */ 31 | static std::string name() { return "constant"; } 32 | }; 33 | 34 | #endif // DISTRIBUTIONS_CONSTANT_DISTRIBUTION_H 35 | -------------------------------------------------------------------------------- /common/distributions/distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "distribution.h" 2 | 3 | // STD headers 4 | #include 5 | 6 | Distribution::Statistics 7 | Distribution::analyzeSamples(const std::vector& v) { 8 | double sum = std::accumulate(std::begin(v), std::end(v), 0.0); 9 | double mean = sum / v.size(); // Sample mean 10 | 11 | double accum = 0.0; 12 | std::for_each (std::begin(v), std::end(v), [&](const 13 | double d) { accum += (d - mean) * (d - mean); }); 14 | double std = sqrt(accum / (v.size() - 1)); // Sample STD 15 | 16 | return Statistics(mean, std); 17 | } 18 | -------------------------------------------------------------------------------- /common/distributions/distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_DISTRIBUTION_H 2 | #define COMMON_DISTRIBUTIONS_DISTRIBUTION_H 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * Base class representing a statistical distribution. 16 | */ 17 | class Distribution { 18 | public: 19 | // Distribution statistics 20 | class Statistics final { 21 | private: 22 | double mean_ = NAN; // Expectation 23 | double std_ = NAN; // Standard deviation 24 | 25 | public: 26 | explicit Statistics() {} 27 | explicit Statistics(const double mean, 28 | const double std) : 29 | mean_(mean), std_(std) {} 30 | // Mutators 31 | void set(const double mean, 32 | const double std) { mean_ = mean; std_ = std; } 33 | 34 | // Accessors 35 | double getStd() const { return std_; } 36 | double getMean() const { return mean_; } 37 | bool isInitialized() const { return (!std::isnan(mean_) && 38 | !std::isnan(std_)); } 39 | }; 40 | 41 | protected: 42 | // Distribution parameters 43 | const std::string kType; 44 | Statistics kSampleStats; 45 | const double kMin = kDblNegInfty; // Minimum value (default: -inf) 46 | const double kMax = kDblPosInfty; // Maximum value (default: +inf) 47 | 48 | // Randomness 49 | std::random_device rd_; 50 | std::mt19937 generator_; 51 | 52 | /** 53 | * Helper method. Given an array of samples (from some 54 | * distribution), returns their sample mean and STD. 55 | */ 56 | static Statistics analyzeSamples(const std::vector& v); 57 | 58 | // Constructors 59 | explicit Distribution(const std::string type) : 60 | kType(type), generator_(rd_()) {} 61 | 62 | explicit Distribution(const std::string type, const double 63 | min, const double max) : kType(type), 64 | kMin(min), kMax(max), generator_(rd_()) {} 65 | public: 66 | virtual ~Distribution() { assert(kSampleStats.isInitialized()); } 67 | DISALLOW_COPY_AND_ASSIGN(Distribution); 68 | 69 | // Accessors 70 | double min() const { return kMin; } 71 | double max() const { return kMax; } 72 | const std::string& type() const { return kType; } 73 | Statistics getSampleStats() const { return kSampleStats; } 74 | 75 | /** 76 | * Print the distribution configuration. 77 | */ 78 | virtual void printConfiguration() const = 0; 79 | 80 | /** 81 | * Sample from the distribution. 82 | */ 83 | virtual double sample() = 0; 84 | }; 85 | 86 | #endif // COMMON_DISTRIBUTIONS_DISTRIBUTION_H 87 | -------------------------------------------------------------------------------- /common/distributions/distribution_factory.cpp: -------------------------------------------------------------------------------- 1 | #include "distribution_factory.h" 2 | 3 | // Library headers 4 | #include "constant_distribution.h" 5 | #include "exponential_distribution.h" 6 | #include "normal_distribution.h" 7 | #include "uniform_distribution.h" 8 | 9 | // STD headers 10 | #include 11 | #include 12 | 13 | /** 14 | * DistributionFactory implementation. 15 | */ 16 | Distribution* DistributionFactory:: 17 | generate(const libconfig::Setting& dist_config) { 18 | Distribution* distribution = nullptr; 19 | 20 | std::string type; // Distribution type 21 | if (!dist_config.lookupValue("type", type)) { 22 | throw std::runtime_error("No distribution type specified."); 23 | } 24 | // Constant distribution 25 | else if (type == ConstantDistribution::name()) { 26 | double value; // Constant value 27 | if (!dist_config.lookupValue("value", value)) { 28 | throw std::runtime_error( 29 | "Must specify 'value' for a constant distribution."); 30 | } 31 | else { distribution = new ConstantDistribution(value); } 32 | } 33 | // Exponential distribution 34 | else if (type == ExponentialDistribution::name()) { 35 | double rate; // Rate of exponential 36 | if (!dist_config.lookupValue("rate", rate)) { 37 | throw std::runtime_error( 38 | "Must specify 'rate' for an exponential distribution."); 39 | } 40 | else { distribution = new ExponentialDistribution(rate); } 41 | } 42 | // Normal distribution 43 | else if (type == NormalDistribution::name()) { 44 | double mu, sigma; // Normal parameters 45 | double min = kDblNegInfty; // Min value 46 | double max = kDblPosInfty; // Max value 47 | 48 | if (!dist_config.lookupValue("mu", mu) || 49 | !dist_config.lookupValue("sigma", sigma)) { 50 | throw std::runtime_error( 51 | "Must specify 'mu' and 'sigma' for a normal distribution."); 52 | } 53 | else { 54 | dist_config.lookupValue("min", min); 55 | dist_config.lookupValue("max", max); 56 | distribution = new NormalDistribution(mu, sigma, min, max); 57 | } 58 | } 59 | // Uniform distribution 60 | else if (type == UniformDistribution::name()) { 61 | double a, b; // Distribution parameters 62 | if (dist_config.lookupValue("lower", a) && 63 | dist_config.lookupValue("upper", b)) { 64 | distribution = new UniformDistribution(a, b); 65 | } 66 | else if (dist_config.lookupValue("mean", a) && 67 | dist_config.lookupValue("std", b)) { 68 | Distribution::Statistics stats(a, b); 69 | distribution = UniformDistribution::from(stats); 70 | } 71 | else { 72 | throw std::runtime_error( 73 | "Must specify either ('lower', 'upper') " 74 | "OR ('mean', 'std') for a uniform distribution."); 75 | } 76 | } 77 | // Unknown distribution 78 | else { throw std::runtime_error( 79 | "Unknown distribution type: " + type + "."); } 80 | 81 | return distribution; 82 | } 83 | -------------------------------------------------------------------------------- /common/distributions/distribution_factory.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_DISTRIBUTION_FACTORY_H 2 | #define COMMON_DISTRIBUTIONS_DISTRIBUTION_FACTORY_H 3 | 4 | // Library headers 5 | #include "distribution.h" 6 | 7 | // Libconfig 8 | #include 9 | 10 | /** 11 | * Factory class for generating distributions. 12 | */ 13 | class DistributionFactory final { 14 | public: 15 | /** 16 | * Returns a distribution corresponding 17 | * to the parameterized configuration. 18 | */ 19 | static Distribution* 20 | generate(const libconfig::Setting& dist_config); 21 | }; 22 | 23 | #endif // COMMON_DISTRIBUTIONS_DISTRIBUTION_FACTORY_H 24 | -------------------------------------------------------------------------------- /common/distributions/exponential_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "exponential_distribution.h" 2 | 3 | // STD headers 4 | #include 5 | #include 6 | 7 | /** 8 | * ExponentialDistribution implementation. 9 | */ 10 | void ExponentialDistribution::printConfiguration() const { 11 | std::cout << "{ type: " << name() << ", " 12 | << std::fixed << std::setprecision(2) 13 | << "rate: " << 1 / kSampleStats.getMean() << " }"; 14 | } 15 | -------------------------------------------------------------------------------- /common/distributions/exponential_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_EXPONENTIAL_DISTRIBUTION_H 2 | #define COMMON_DISTRIBUTIONS_EXPONENTIAL_DISTRIBUTION_H 3 | 4 | // Library headers 5 | #include "distribution.h" 6 | 7 | // STD headers 8 | #include 9 | 10 | /** 11 | * Represents an exponential distribution. 12 | */ 13 | class ExponentialDistribution : public Distribution { 14 | private: 15 | // Implementation 16 | std::exponential_distribution dist_; 17 | 18 | public: 19 | virtual ~ExponentialDistribution() {} 20 | explicit ExponentialDistribution(const double rate) : 21 | Distribution(name(), 0, kDblPosInfty), dist_(rate) { 22 | if (rate <= 0) { 23 | throw std::invalid_argument("Rate must be positive"); 24 | } 25 | const double rate_inv = (1 / rate); 26 | kSampleStats.set(rate_inv, rate_inv); 27 | } 28 | 29 | /** 30 | * Print the distribution configuration. 31 | */ 32 | virtual void printConfiguration() const override; 33 | 34 | /** 35 | * Sample from the distribution. 36 | */ 37 | double sample() override { return dist_(generator_); } 38 | 39 | /** 40 | * Distribution name. 41 | */ 42 | static std::string name() { return "exponential"; } 43 | }; 44 | 45 | #endif // COMMON_DISTRIBUTIONS_EXPONENTIAL_DISTRIBUTION_H 46 | -------------------------------------------------------------------------------- /common/distributions/normal_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "normal_distribution.h" 2 | 3 | // STD headers 4 | #include 5 | #include 6 | 7 | /** 8 | * NormalDistribution implementation. 9 | */ 10 | NormalDistribution::NormalDistribution( 11 | const double mu, const double sigma, 12 | const double min, const double max) : 13 | Distribution(name(), min, max), dist_(mu, sigma) { 14 | updateSampleParameters(); 15 | } 16 | 17 | void NormalDistribution::printConfiguration() const { 18 | std::cout << "{ type: " << name() << ", " 19 | << std::fixed << std::setprecision(2) 20 | << "min: " << min() << ", " 21 | << "max: " << max() << ", " 22 | << "mu: " << kSampleStats.getMean() << ", " 23 | << "sigma: " << kSampleStats.getStd() << " }"; 24 | } 25 | 26 | // Internal helper method. Computes sample statistics 27 | // for the (possibly truncated) Gaussian distribution. 28 | void NormalDistribution::updateSampleParameters() { 29 | Statistics sample_stats{dist_.mean(), dist_.stddev()}; 30 | 31 | // Truncated distribution 32 | if (isTruncated()) { 33 | constexpr size_t kMaxNumSamples = 1000000; 34 | std::vector v(kMaxNumSamples, 0); // Samples 35 | for (size_t idx = 0; idx < v.size(); idx++) { v[idx] = sample(); } 36 | sample_stats = analyzeSamples(v); // Compute the sample statistics 37 | } 38 | // Update the sample stats 39 | kSampleStats = sample_stats; 40 | } 41 | 42 | /** 43 | * Returns whether this a truncated Gaussian. 44 | */ 45 | bool NormalDistribution::isTruncated() const { 46 | return (kMin != kDblNegInfty || kMax != kDblPosInfty); 47 | } 48 | 49 | /** 50 | * Sample from the distribution. 51 | */ 52 | double NormalDistribution::sample() { 53 | while (true) { 54 | double sample = dist_(generator_); 55 | if (sample >= kMin && sample <= kMax) { return sample; } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /common/distributions/normal_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_NORMAL_DISTRIBUTION_H 2 | #define COMMON_DISTRIBUTIONS_NORMAL_DISTRIBUTION_H 3 | 4 | // Library headers 5 | #include "distribution.h" 6 | 7 | /** 8 | * Represents a normal distribution. 9 | */ 10 | class NormalDistribution : public Distribution { 11 | private: 12 | // Implementation 13 | std::normal_distribution dist_; 14 | 15 | // Internal helper methods 16 | void updateSampleParameters(); 17 | 18 | public: 19 | virtual ~NormalDistribution() {} 20 | explicit NormalDistribution(const double mu, const double sigma, 21 | const double min=kDblNegInfty, 22 | const double max=kDblPosInfty); 23 | /** 24 | * Print the distribution configuration. 25 | */ 26 | virtual void printConfiguration() const override; 27 | 28 | // Accessors 29 | bool isTruncated() const; 30 | double sample() override; 31 | 32 | /** 33 | * Distribution name. 34 | */ 35 | static std::string name() { return "normal"; } 36 | }; 37 | 38 | #endif // COMMON_DISTRIBUTIONS_NORMAL_DISTRIBUTION_H 39 | -------------------------------------------------------------------------------- /common/distributions/uniform_distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "uniform_distribution.h" 2 | 3 | // STD headers 4 | #include 5 | #include 6 | 7 | UniformDistribution:: 8 | UniformDistribution(const double a, const double b) : 9 | Distribution(name(), a, b), dist_(a, b) { 10 | double mean = (a + b) / 2; 11 | double std = (b - a) / sqrt(12); 12 | 13 | // Update sample stats 14 | kSampleStats.set(mean, std); 15 | } 16 | 17 | UniformDistribution* 18 | UniformDistribution::from(const Distribution::Statistics stats) { 19 | double b = stats.getMean() + (sqrt(3) * stats.getStd()); 20 | double a = (2 * stats.getMean()) - b; 21 | 22 | // Return a distribution with the given parameters 23 | return new UniformDistribution(a, b); 24 | } 25 | 26 | void UniformDistribution::printConfiguration() const { 27 | std::cout << "{ type: " << name() << ", " 28 | << "lower: " << min() << ", " 29 | << "upper: " << max() << " }"; 30 | } 31 | -------------------------------------------------------------------------------- /common/distributions/uniform_distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_DISTRIBUTIONS_UNIFORM_DISTRIBUTION_H 2 | #define COMMON_DISTRIBUTIONS_UNIFORM_DISTRIBUTION_H 3 | 4 | // Library headers 5 | #include "distribution.h" 6 | 7 | /** 8 | * Represents a uniform distribution. 9 | */ 10 | class UniformDistribution : public Distribution { 11 | private: 12 | // Implementation 13 | std::uniform_real_distribution dist_; 14 | 15 | public: 16 | explicit UniformDistribution(const double a, const double b); 17 | virtual ~UniformDistribution() {} 18 | 19 | /** 20 | * Factory method. Generates a uniform distribution 21 | * with the given distribution stats (mean, STD). 22 | */ 23 | static UniformDistribution* from(const Statistics stats); 24 | 25 | /** 26 | * Print the distribution configuration. 27 | */ 28 | virtual void printConfiguration() const override; 29 | 30 | /** 31 | * Sample from the distribution. 32 | */ 33 | double sample() override { return dist_(generator_); } 34 | 35 | /** 36 | * Distribution name. 37 | */ 38 | static std::string name() { return "uniform"; } 39 | }; 40 | 41 | #endif // COMMON_DISTRIBUTIONS_UNIFORM_DISTRIBUTION_H 42 | -------------------------------------------------------------------------------- /common/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_MACROS_H 2 | #define COMMON_MACROS_H 3 | 4 | // STD headers 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Macros. 11 | */ 12 | #define LIKELY(x) __builtin_expect(!!(x), 1) 13 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 14 | #define SUPPRESS_UNUSED_WARNING(a) ((void) a) 15 | 16 | // Assert macros (define DISABLE_ASSERT to disable assertion). 17 | #ifdef DISABLE_ASSERT 18 | #define SP_ASSERT(exp) do {} while (0) 19 | #else 20 | #define SP_ASSERT(exp) assert(exp) 21 | #endif 22 | 23 | // Macro to generate default methods 24 | #define DEFAULT_CTOR_AND_DTOR(TypeName) \ 25 | TypeName() = default; \ 26 | ~TypeName() = default 27 | 28 | // Macro to disallow copy/assignment 29 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 30 | TypeName(const TypeName&) = delete; \ 31 | void operator=(const TypeName&) = delete 32 | 33 | /** 34 | * Constant parameters. 35 | */ 36 | constexpr uint8_t kBitsPerByte = 8; 37 | constexpr double kInvalidJobSize = -1; 38 | constexpr uint64_t kBitsPerGb = 1000000000; 39 | constexpr uint64_t kMicrosecsPerSec = 1000000; 40 | constexpr uint64_t kNanosecsPerSec = 1000000000; 41 | constexpr double kDblPosInfty = std::numeric_limits::infinity(); 42 | constexpr double kDblNegInfty = -std::numeric_limits::infinity(); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /common/tsc_clock.cpp: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------- 2 | // Copyright (C) 2016-2018 Cisco and/or its affiliates. All rights reserved. 3 | // 4 | // This program is free software; you can redistribute it and/or modify it 5 | // under the terms of the GNU General Public License Version 2 as published 6 | // by the Free Software Foundation. You may not use, modify or distribute 7 | // this program under any other version of the GNU General Public License. 8 | // 9 | // This program is distributed in the hope that it will be useful, but 10 | // WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | // General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License along 15 | // with this program; if not, write to the Free Software Foundation, Inc., 16 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | //-------------------------------------------------------------------------- 18 | // tsc_clock.cc author Russ Combs 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include "tsc_clock.h" 25 | 26 | #include 27 | 28 | long clock_scale() 29 | { 30 | static thread_local long tpus = 0; // ticks / usec 31 | 32 | if ( !tpus ) 33 | { 34 | struct timespec one_sec = { 1, 0 }; 35 | uint64_t start = TscClock::counter(); 36 | nanosleep(&one_sec, nullptr); 37 | uint64_t end = TscClock::counter(); 38 | tpus = (long)((end - start)/1e6); 39 | } 40 | return tpus; 41 | } 42 | -------------------------------------------------------------------------------- /common/tsc_clock.h: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------------------------------- 2 | // Copyright (C) 2016-2018 Cisco and/or its affiliates. All rights reserved. 3 | // 4 | // This program is free software; you can redistribute it and/or modify it 5 | // under the terms of the GNU General Public License Version 2 as published 6 | // by the Free Software Foundation. You may not use, modify or distribute 7 | // this program under any other version of the GNU General Public License. 8 | // 9 | // This program is distributed in the hope that it will be useful, but 10 | // WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | // General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License along 15 | // with this program; if not, write to the Free Software Foundation, Inc., 16 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | //-------------------------------------------------------------------------- 18 | // tsc_clock.h author Russ Combs 19 | 20 | #ifndef COMMON_TSC_CLOCK_H 21 | #define COMMON_TSC_CLOCK_H 22 | 23 | // the STL chrono clocks are kinda heavy so we use the time stamp counter 24 | // where available (x86 with rdtsc support). this wasn't always a good 25 | // choice on multi-core systems but most now have rdtscp, constant_tsc, 26 | // tsc_reliable, and nonstop_tsc. note that we don't worry about exact 27 | // instruction sequencing. 28 | // 29 | // references: 30 | // http://stackoverflow.com/questions/275004/timer-function-to-provide-time-in-nano-seconds-using-c 31 | // http://stackoverflow.com/questions/7935518/is-clock-gettime-adequate-for-submicrosecond-timing 32 | // 33 | // this clock stores ticks, not actual time values. use ticks during runtime 34 | // convert from/to usecs at startup/shutdown. see clock_defs.h. 35 | 36 | #include 37 | 38 | struct TscClock 39 | { 40 | typedef uint64_t duration; 41 | typedef uint64_t time_point; 42 | 43 | static const bool is_steady = true; 44 | 45 | static uint64_t counter() 46 | { 47 | #if defined(__aarch64__) 48 | uint64_t ticks; 49 | 50 | asm volatile("mrs %0, CNTVCT_EL0" : "=r" (ticks)); 51 | return ticks; 52 | #else 53 | // Default to x86, other archs will compile error anyway 54 | uint32_t lo, hi; 55 | asm volatile("rdtsc" : "=a" (lo), "=d" (hi)); 56 | return ((uint64_t)hi << 32) | lo; 57 | #endif 58 | } 59 | 60 | static time_point now() noexcept 61 | { 62 | return time_point(duration(counter())); 63 | } 64 | }; 65 | 66 | long clock_scale(); 67 | 68 | #endif // COMMON_TSC_CLOCK_H -------------------------------------------------------------------------------- /common/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | // Library headers 4 | #include "macros.h" 5 | 6 | // STD headers 7 | #include 8 | 9 | /** 10 | * Helper function. Given a packet rate and expected packet size, 11 | * returns the inter-arrival time for the corresponding traffic. 12 | */ 13 | double getTrafficInterArrivalTimeInNs( 14 | const double traffic_rate_in_bits_per_sec, 15 | const uint32_t expected_packet_size_in_bits) { 16 | // Disable traffic generation 17 | if (traffic_rate_in_bits_per_sec == 0) { return kDblPosInfty; } 18 | 19 | return ((kNanosecsPerSec * expected_packet_size_in_bits) / 20 | traffic_rate_in_bits_per_sec); 21 | } 22 | 23 | /** 24 | * Helper string parsing functions. Emulates Python's str.split(). 25 | */ 26 | bool endsWith(const std::string& s, const std::string& suffix) { 27 | return (s.size() >= suffix.size() && 28 | s.substr(s.size() - suffix.size()) == suffix); 29 | } 30 | 31 | std::vector split(const std::string& s, 32 | const std::string& delimiter) { 33 | std::vector tokens; 34 | for (size_t start = 0, end; start < s.length(); 35 | start = end + delimiter.length()) { 36 | size_t position = s.find(delimiter, start); 37 | end = position != std::string::npos ? position : s.length(); 38 | 39 | std::string token = s.substr(start, end - start); 40 | tokens.push_back(token); 41 | } 42 | if (s.empty() || endsWith(s, delimiter)) { 43 | tokens.push_back(""); 44 | } 45 | return tokens; 46 | } 47 | 48 | /** 49 | * Helper function. Returns whether a and b are equal 50 | * (within floating-point error margin). 51 | */ 52 | bool DoubleApproxEqual(const double a, const double b, 53 | const double epsilon) { 54 | return fabs(a - b) < epsilon; 55 | } 56 | -------------------------------------------------------------------------------- /common/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_UTILS_H 2 | #define COMMON_UTILS_H 3 | 4 | // STD headers 5 | #include 6 | #include 7 | 8 | /** 9 | * Represents a min-heap entry. 10 | */ 11 | template class MinHeapEntry { 12 | private: 13 | Tag tag_; // Tag corresponding to this entry 14 | double insert_time_; // Time (in ns) of insertion 15 | Metric primary_metric_; // The primary priority metric 16 | 17 | public: 18 | explicit MinHeapEntry(const Tag& tag, const Metric& metric, const double in_time=0) : 19 | tag_(tag), insert_time_(in_time), primary_metric_(metric) {} 20 | // Accessors 21 | const Tag& tag() const { return tag_; } 22 | double getInsertTime() const { return insert_time_; } 23 | Metric getPrimaryMetric() const { return primary_metric_; } 24 | 25 | // Comparator 26 | bool operator<(const MinHeapEntry& other) const { 27 | // Sort by the primary metric 28 | if (primary_metric_ != other.primary_metric_) { 29 | return primary_metric_ > other.primary_metric_; 30 | } 31 | // If tied, sort by insertion time 32 | return insert_time_ >= other.insert_time_; 33 | } 34 | }; 35 | 36 | /** 37 | * Helper function. Given a packet rate and expected packet size, 38 | * returns the inter-arrival time for the corresponding traffic. 39 | */ 40 | double getTrafficInterArrivalTimeInNs( 41 | const double traffic_rate_in_bits_per_sec, 42 | const uint32_t expected_packet_size_in_bits); 43 | 44 | /** 45 | * Helper string parsing functions. Emulates Python's str.split(). 46 | */ 47 | bool endsWith(const std::string& s, const std::string& suffix); 48 | std::vector split(const std::string& s, 49 | const std::string& delimiter); 50 | 51 | /** 52 | * Helper function. Returns whether a and b are equal 53 | * (within floating-point error margin). 54 | */ 55 | bool DoubleApproxEqual(const double a, const double b, 56 | const double epsilon=1e-6); 57 | 58 | #endif // COMMON_UTILS_H 59 | -------------------------------------------------------------------------------- /scheduler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # DPDK 2 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 3 | find_package(dpdk REQUIRED) 4 | 5 | # Headers 6 | include_directories(.) 7 | 8 | # Sources 9 | add_subdirectory(benchmark) 10 | -------------------------------------------------------------------------------- /scheduler/benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Headers 2 | include_directories(.) 3 | 4 | # Sources 5 | add_subdirectory(pktgen) 6 | add_subdirectory(server) 7 | -------------------------------------------------------------------------------- /scheduler/benchmark/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_PACKET_H 2 | #define SCHEDULER_BENCHMARK_PACKET_H 3 | 4 | // DPDK headers 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Traffic parameters. 11 | */ 12 | // Packet size 13 | constexpr uint32_t kCommonPSize = (sizeof(struct rte_ether_hdr) + 14 | sizeof(struct rte_ipv4_hdr) + 15 | sizeof(struct rte_udp_hdr)); 16 | 17 | constexpr uint32_t kInnocentMaxPayloadSize = 1450; 18 | constexpr uint32_t kInnocentAvgPayloadSize = 1208; 19 | constexpr uint32_t kInnocentStdPayloadSize = 100; 20 | constexpr uint32_t kInnocentAvgPSizeInBytes = ( 21 | kCommonPSize + kInnocentAvgPayloadSize); 22 | 23 | constexpr uint32_t kInnocentMinPayloadSize = ( 24 | kInnocentAvgPayloadSize - ( 25 | kInnocentMaxPayloadSize - 26 | kInnocentAvgPayloadSize) 27 | ); 28 | 29 | constexpr uint32_t kAttackPayloadSize = 22; 30 | constexpr uint32_t kAttackPSizeInBytes = ( 31 | kCommonPSize + kAttackPayloadSize); 32 | 33 | // Job size 34 | constexpr uint32_t kInnocentAvgJSizeInNs = 1000; 35 | constexpr uint32_t kInnocentStdJSizeInNs = 100; 36 | constexpr uint32_t kAttackJSizeInNs = 10000; 37 | 38 | // Packet parameters 39 | #define PAYLOAD_JSIZE_OFFSET 0 40 | #define PAYLOAD_CLASS_OFFSET 4 41 | enum PacketClass { ATTACK = 0, INNOCENT }; 42 | 43 | /** 44 | * Packet parameters. 45 | */ 46 | struct PacketParams { 47 | uint8_t class_tag; // Packet class 48 | uint32_t jsize_ns; // Job size (in ns) 49 | uint32_t psize_bytes; // Packet size (in bytes) 50 | }; 51 | 52 | /** 53 | * Returns parameters corresponding to the given packet mbuf. 54 | */ 55 | static inline PacketParams getPacketParams(rte_mbuf* mbuf) { 56 | char* payload = rte_pktmbuf_mtod_offset( 57 | mbuf, char*, 58 | (sizeof(struct rte_ether_hdr) + 59 | sizeof(struct rte_ipv4_hdr) + 60 | sizeof(struct rte_udp_hdr))); 61 | 62 | // Fetch the job size and class 63 | uint32_t jsize_ns = rte_be_to_cpu_32( 64 | *((uint32_t*) (payload + PAYLOAD_JSIZE_OFFSET))); 65 | 66 | uint8_t class_tag = ( 67 | *((uint8_t*) (payload + PAYLOAD_CLASS_OFFSET))); 68 | 69 | // Return the packet parameters 70 | return PacketParams{class_tag, jsize_ns, mbuf->pkt_len}; 71 | } 72 | 73 | #endif // SCHEDULER_BENCHMARK_PACKET_H 74 | -------------------------------------------------------------------------------- /scheduler/benchmark/pktgen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # DPDK 2 | include_directories(${dpdk_INCLUDE_DIRS}) 3 | set(RTE_TARGET x86_64-native-linuxapp-gcc) 4 | 5 | # Sources 6 | add_executable(sched_benchmark_pktgen 7 | pktgen.cpp 8 | ) 9 | 10 | # Link libraries 11 | target_link_libraries(sched_benchmark_pktgen common) 12 | target_link_libraries(sched_benchmark_pktgen distributions) 13 | target_link_libraries(sched_benchmark_pktgen ${dpdk_LIBRARIES}) 14 | -------------------------------------------------------------------------------- /scheduler/benchmark/scripts/plot_results.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import itertools 3 | 4 | # PyPlot static parameters 5 | plt.rcParams['axes.grid'] = True 6 | plt.rcParams['grid.alpha'] = 0.25 7 | plt.rcParams['axes.axisbelow'] = True 8 | 9 | prop_cycle = plt.rcParams['axes.prop_cycle'] 10 | colors_all = prop_cycle.by_key()['color'] 11 | colors = itertools.cycle((colors_all[0], 12 | colors_all[3], 13 | colors_all[2], 14 | colors_all[1])) 15 | 16 | def autolabel(rects, axis=None): 17 | """Attach a text label above each bar displaying its height.""" 18 | if axis is None: axis = plt 19 | for rect in rects: 20 | height = rect.get_height() 21 | label = ("{:.2f}".format(height) if height < 10.0 22 | else "{:.1f}".format(height)) 23 | 24 | axis.text(rect.get_x() + rect.get_width() / 2., height + 0.05, 25 | label, ha='center', va='bottom', fontsize=8) 26 | 27 | def plot_results(): 28 | """Plots results.""" 29 | goodputs = dict() 30 | # Update this to plot new results 31 | attack_rates = [0, 0.1, 1, 3] 32 | goodputs["FCFS"] = [9.94, 5.40, 0.74, 0.38] 33 | goodputs["Fibonacci Heap (drop-tail)"] = [9.94, 5.87, 0.73, 0.38] 34 | goodputs["DEPQ (drop-max)"] = [9.95, 9.95, 5.64, 2.65] 35 | goodputs["Hierarchical FFS Queue"] = [9.95, 9.95, 9.94, 9.86] 36 | 37 | figure, axis = plt.subplots() 38 | 39 | width = 0.1 40 | handles = [] 41 | x_labels = [] 42 | x_centers = [] 43 | start_x = width * 2 44 | 45 | for rate_idx, attack_rate in enumerate(attack_rates): 46 | for idx, tag in enumerate(["FCFS", "Fibonacci Heap (drop-tail)", 47 | "DEPQ (drop-max)", "Hierarchical FFS Queue"]): 48 | container = axis.bar([start_x + (idx * width)], 49 | [goodputs[tag][rate_idx]], 50 | width, color=next(colors), label=tag) 51 | autolabel(container) 52 | if (rate_idx == 0): handles.append(container) 53 | 54 | x_center = start_x + (width / 2) 55 | x_centers.append(x_center) 56 | x_labels.append("{}Gbps".format(attack_rate)) 57 | start_x += width + (4 * width) 58 | 59 | axis.set_xticks(x_centers) 60 | axis.set_ylim((axis.get_ylim()[0], 12)) 61 | axis.set_xticklabels(x_labels, fontsize="small") 62 | axis.set_ylabel("Goodput (Gbps)", fontsize="medium") 63 | axis.set_xlabel("Attack Rate (Gbps)", fontsize="medium") 64 | 65 | # Shrink current axis's height by 10% on the top 66 | box = axis.get_position() 67 | axis.set_position([box.x0, box.y0 + box.height * 0.1, 68 | box.width, box.height * 0.9]) 69 | 70 | # Put a legend above current axis 71 | legend = axis.legend(handles=handles, loc="upper center", ncol=2, 72 | bbox_to_anchor=(0.5, 1.3), fontsize="small") 73 | axis.add_artist(legend) 74 | 75 | plt.subplots_adjust(top=0.820, bottom=0.170, 76 | left=0.100, right=0.995, 77 | hspace=0.200, wspace=0.200) 78 | plt.show() 79 | plt.close(figure) 80 | 81 | if __name__ == "__main__": 82 | plot_results() 83 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # DPDK 2 | include_directories(${dpdk_INCLUDE_DIRS}) 3 | set(RTE_TARGET x86_64-native-linuxapp-gcc) 4 | 5 | # Sources 6 | include_directories(.) 7 | add_executable(sched_benchmark_server 8 | server.cpp 9 | ) 10 | 11 | # Subdirectories 12 | add_subdirectory(policies) 13 | 14 | # Link libraries 15 | target_link_libraries(sched_benchmark_server common) 16 | target_link_libraries(sched_benchmark_server sched_policies) 17 | target_link_libraries(sched_benchmark_server ${dpdk_LIBRARIES}) 18 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(sched_policies STATIC 4 | policy_fcfs.cpp 5 | policy_wsjf_dropmax.cpp 6 | policy_wsjf_droptail.cpp 7 | policy_wsjf_hffs.cpp 8 | ) 9 | 10 | target_link_libraries(sched_policies ${dpdk_LIBRARIES}) 11 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_fcfs.cpp: -------------------------------------------------------------------------------- 1 | #include "policy_fcfs.h" 2 | 3 | PolicyFCFS::PolicyFCFS(struct rte_mempool* mbuf_pool, 4 | struct rte_ring* process_ring) : 5 | process_ring_(process_ring), 6 | mbuf_pool_(mbuf_pool) {} 7 | 8 | PolicyFCFS::~PolicyFCFS() {} 9 | 10 | /** 11 | * Returns the name of this scheduling policy. 12 | */ 13 | std::string PolicyFCFS::name() { return "fcfs"; } 14 | 15 | /** 16 | * Dequeues a burst of packets from the packet 17 | * queue and pushes them onto the process ring. 18 | */ 19 | void PolicyFCFS::scheduleBurst() {} 20 | 21 | /** 22 | * Enqueues a burst of packets in to the RX packet queue. 23 | * If the queue becomes full, also deallocates the mbufs 24 | * corresponding to the dropped packets. 25 | * 26 | * @param mbufs Pointer to an array of mbufs to enqueue. 27 | * @param num_mbufs Number of mbufs to enqueue. 28 | */ 29 | void PolicyFCFS::enqueueBurst(struct rte_mbuf** mbufs, 30 | const uint16_t num_mbufs) { 31 | const uint16_t num_enqueued = rte_ring_sp_enqueue_burst( 32 | process_ring_, (void**) mbufs, num_mbufs, NULL); 33 | 34 | for (auto idx = num_enqueued; idx < num_mbufs; idx++) { 35 | rte_pktmbuf_free(mbufs[idx]); 36 | } 37 | num_rx_ += num_enqueued; 38 | } 39 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_fcfs.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_FCFS_H 2 | #define SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_FCFS_H 3 | 4 | // STD headers 5 | #include 6 | #include 7 | 8 | // DPDK headers 9 | #include 10 | #include 11 | #include 12 | 13 | /** 14 | * Implements the FCFS scheduling policy. 15 | */ 16 | class PolicyFCFS { 17 | private: 18 | uint64_t num_rx_ = 0; 19 | struct rte_ring* process_ring_; 20 | struct rte_mempool* mbuf_pool_; 21 | 22 | public: 23 | ~PolicyFCFS(); 24 | PolicyFCFS(struct rte_mempool* mbuf_pool, 25 | struct rte_ring* process_ring); 26 | 27 | static std::string name(); 28 | 29 | void scheduleBurst(); 30 | void enqueueBurst(struct rte_mbuf** mbufs, 31 | const uint16_t num_mbufs); 32 | }; 33 | 34 | #endif // SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_FCFS_H 35 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_dropmax.cpp: -------------------------------------------------------------------------------- 1 | #include "policy_wsjf_dropmax.h" 2 | 3 | // Library headers 4 | #include "benchmark/packet.h" 5 | #include "scheduler.hpp" 6 | 7 | /** 8 | * Returns the name of this scheduling policy. 9 | */ 10 | std::string PolicyWSJFFibonacciDropMax::name() { 11 | return "wsjf_drop_max"; 12 | } 13 | 14 | PolicyWSJFFibonacciDropMax::PolicyWSJFFibonacciDropMax( 15 | struct rte_mempool* mbuf_pool, struct rte_ring* process_ring) : 16 | queue_(SCHEDULER_QUEUE_SIZE), process_ring_(process_ring), 17 | mbuf_pool_(mbuf_pool) {} 18 | 19 | /** 20 | * Computes and returns the packet weight. 21 | */ 22 | inline double getPacketWeight(const PacketParams params) { 23 | return ((double) params.jsize_ns) / params.psize_bytes; 24 | } 25 | 26 | /** 27 | * Dequeues a burst of packets from the packet 28 | * queue and pushes them onto the process ring. 29 | */ 30 | void PolicyWSJFFibonacciDropMax::scheduleBurst() { 31 | // Enque packets into the process ring until either the 32 | // packet queue becomes empty or the ring becomes full. 33 | int free_slots = rte_ring_free_count(process_ring_); 34 | while (!queue_.empty() && (free_slots > 0)) { 35 | rte_ring_enqueue(process_ring_, queue_.pop()); 36 | free_slots--; 37 | } 38 | } 39 | 40 | /** 41 | * Enqueues a burst of packets in to the RX packet queue. 42 | * If the queue becomes full, also deallocates the mbufs 43 | * corresponding to the dropped packets. 44 | * 45 | * @param mbufs Pointer to an array of mbufs to enqueue. 46 | * @param num_mbufs Number of mbufs to enqueue. 47 | */ 48 | void PolicyWSJFFibonacciDropMax::enqueueBurst( 49 | struct rte_mbuf** mbufs, const uint16_t num_mbufs) { 50 | for (uint16_t idx = 0; idx < num_mbufs; idx++) { 51 | rte_mbuf* dropped_entry = nullptr; 52 | 53 | auto weight = getPacketWeight(getPacketParams(mbufs[idx])); 54 | if (queue_.push(mbufs[idx], weight, dropped_entry)) { 55 | rte_pktmbuf_free(dropped_entry); 56 | } 57 | else { num_rx_++; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_dropmax.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPMAX_H 2 | #define SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPMAX_H 3 | 4 | // Library headers 5 | #include "heaps/bounded_heap.hpp" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | 11 | // DPDK headers 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * Implements the WSJF scheduling policy using a pair of 18 | * Fibonacci heap, dropping packets corresponding to the 19 | * max heap entries. 20 | */ 21 | class PolicyWSJFFibonacciDropMax { 22 | private: 23 | uint64_t num_rx_ = 0; 24 | BoundedHeap queue_; 25 | struct rte_ring* process_ring_; 26 | struct rte_mempool* mbuf_pool_; 27 | 28 | public: 29 | static std::string name(); 30 | PolicyWSJFFibonacciDropMax(struct rte_mempool* mbuf_pool, 31 | struct rte_ring* process_ring); 32 | void scheduleBurst(); 33 | void enqueueBurst(struct rte_mbuf** mbufs, 34 | const uint16_t num_mbufs); 35 | }; 36 | 37 | #endif // SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPMAX_H 38 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_droptail.cpp: -------------------------------------------------------------------------------- 1 | #include "policy_wsjf_droptail.h" 2 | 3 | // Library headers 4 | #include "benchmark/packet.h" 5 | #include "scheduler.hpp" 6 | 7 | /** 8 | * Returns the name of this scheduling policy. 9 | */ 10 | std::string PolicyWSJFFibonacciDropTail::name() { 11 | return "wsjf_drop_tail"; 12 | } 13 | 14 | PolicyWSJFFibonacciDropTail::PolicyWSJFFibonacciDropTail( 15 | struct rte_mempool* mbuf_pool, struct rte_ring* process_ring) : 16 | process_ring_(process_ring), mbuf_pool_(mbuf_pool), queue_() {} 17 | 18 | /** 19 | * Computes and returns the packet weight. 20 | */ 21 | inline double getPacketWeight(const PacketParams params) { 22 | return ((double) params.jsize_ns) / params.psize_bytes; 23 | } 24 | 25 | /** 26 | * Dequeues a burst of packets from the packet 27 | * queue and pushes them onto the process ring. 28 | */ 29 | void PolicyWSJFFibonacciDropTail::scheduleBurst() { 30 | // Enque packets into the process ring until either the 31 | // packet queue becomes empty or the ring becomes full. 32 | int free_slots = rte_ring_free_count(process_ring_); 33 | while (!queue_.empty() && (free_slots > 0)) { 34 | rte_ring_enqueue(process_ring_, queue_.pop()); 35 | free_slots--; 36 | } 37 | } 38 | 39 | /** 40 | * Enqueues a burst of packets in to the RX packet queue. 41 | * If the queue becomes full, also deallocates the mbufs 42 | * corresponding to the dropped packets. 43 | * 44 | * @param mbufs Pointer to an array of mbufs to enqueue. 45 | * @param num_mbufs Number of mbufs to enqueue. 46 | */ 47 | void PolicyWSJFFibonacciDropTail::enqueueBurst( 48 | struct rte_mbuf** mbufs, const uint16_t num_mbufs) { 49 | uint16_t num_enqueued = 0; 50 | while ((num_enqueued < num_mbufs) && 51 | (queue_.size() < SCHEDULER_QUEUE_SIZE)) { 52 | // Insert the current mbuf into the packet queue 53 | auto params = getPacketParams(mbufs[num_enqueued]); 54 | queue_.push(mbufs[num_enqueued], getPacketWeight(params)); 55 | 56 | // Increment the enque count 57 | num_enqueued++; 58 | } 59 | // Deallocate the mbufs corresponding to dropped packets 60 | for (auto idx = num_enqueued; idx < num_mbufs; idx++) { 61 | rte_pktmbuf_free(mbufs[idx]); 62 | } 63 | num_rx_ += num_enqueued; 64 | } 65 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_droptail.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPTAIL_H 2 | #define SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPTAIL_H 3 | 4 | // Library headers 5 | #include "heaps/fibonacci_heap.hpp" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | 11 | // DPDK headers 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * Implements the WSJF scheduling policy using a 18 | * Fibonacci heap, dropping packets at the tail. 19 | */ 20 | class PolicyWSJFFibonacciDropTail { 21 | private: 22 | uint64_t num_rx_ = 0; 23 | struct rte_ring* process_ring_; 24 | struct rte_mempool* mbuf_pool_; 25 | FibonacciHeap queue_; 26 | 27 | public: 28 | static std::string name(); 29 | PolicyWSJFFibonacciDropTail(struct rte_mempool* mbuf_pool, 30 | struct rte_ring* process_ring); 31 | void scheduleBurst(); 32 | void enqueueBurst(struct rte_mbuf** mbufs, 33 | const uint16_t num_mbufs); 34 | }; 35 | 36 | #endif // SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_DROPTAIL_H 37 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_hffs.cpp: -------------------------------------------------------------------------------- 1 | #include "policy_wsjf_hffs.h" 2 | 3 | // Library headers 4 | #include "benchmark/packet.h" 5 | #include "scheduler.hpp" 6 | 7 | // Queue configuration 8 | constexpr uint32_t kNumTotalPriorityBuckets = (32 * 32 * 32 * 32); 9 | constexpr uint32_t kMaxWeight = ( 10 | (kAttackJSizeInNs + kAttackPSizeInBytes - 1) / kAttackPSizeInBytes); 11 | constexpr uint32_t kScaleFactor = (kNumTotalPriorityBuckets / kMaxWeight); 12 | 13 | /** 14 | * Returns the name of this scheduling policy. 15 | */ 16 | std::string PolicyWSJFFHierarchicalFFS::name() { 17 | return "wsjf_hffs"; 18 | } 19 | 20 | PolicyWSJFFHierarchicalFFS::PolicyWSJFFHierarchicalFFS( 21 | struct rte_mempool* mbuf_pool, struct rte_ring* process_ring) : 22 | process_ring_(process_ring), mbuf_pool_(mbuf_pool), 23 | queue_(kNumTotalPriorityBuckets, kScaleFactor) {} 24 | 25 | /** 26 | * Dequeues a burst of packets from the packet 27 | * queue and pushes them onto the process ring. 28 | */ 29 | void PolicyWSJFFHierarchicalFFS::scheduleBurst() { 30 | // Enque packets into the process ring until either the 31 | // packet queue becomes empty or the ring becomes full. 32 | int free_slots = rte_ring_free_count(process_ring_); 33 | while (!queue_.empty() && (free_slots > 0)) { 34 | rte_ring_enqueue(process_ring_, queue_.popMin()); 35 | free_slots--; 36 | } 37 | } 38 | 39 | /** 40 | * Enqueues a burst of packets in to the RX packet queue. 41 | * If the queue becomes full, also deallocates the mbufs 42 | * corresponding to the dropped packets. 43 | * 44 | * @param mbufs Pointer to an array of mbufs to enqueue. 45 | * @param num_mbufs Number of mbufs to enqueue. 46 | */ 47 | void PolicyWSJFFHierarchicalFFS::enqueueBurst( 48 | struct rte_mbuf** mbufs, const uint16_t num_mbufs) { 49 | for (auto idx = 0; idx < num_mbufs; idx++) { 50 | auto p = getPacketParams(mbufs[idx]); 51 | auto weight = (HierarchicalFindFirstSetQueue:: 52 | UnscaledWeight{p.jsize_ns, p.psize_bytes}); 53 | 54 | queue_.push(mbufs[idx], weight); 55 | num_rx_++; 56 | } 57 | // Deallocate the mbufs corresponding to dropped packets 58 | while (queue_.size() > SCHEDULER_QUEUE_SIZE) { 59 | rte_pktmbuf_free(queue_.popMax()); 60 | num_rx_--; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/policy_wsjf_hffs.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_HFFS_H 2 | #define SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_HFFS_H 3 | 4 | // Library headers 5 | #include "heaps/hffs_queue/software/hffs_queue.hpp" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | 11 | // DPDK headers 12 | #include 13 | #include 14 | #include 15 | 16 | /** 17 | * Implements the WSJF scheduling policy using 18 | * a Hierarchical Find-First Set (FFS) queue. 19 | */ 20 | class PolicyWSJFFHierarchicalFFS { 21 | private: 22 | uint64_t num_rx_ = 0; 23 | struct rte_ring* process_ring_; 24 | struct rte_mempool* mbuf_pool_; 25 | HierarchicalFindFirstSetQueue queue_; 26 | 27 | public: 28 | static std::string name(); 29 | PolicyWSJFFHierarchicalFFS(struct rte_mempool* mbuf_pool, 30 | struct rte_ring* process_ring); 31 | void scheduleBurst(); 32 | void enqueueBurst(struct rte_mbuf** mbufs, 33 | const uint16_t num_mbufs); 34 | }; 35 | 36 | #endif // SCHEDULER_BENCHMARK_SERVER_POLICIES_POLICY_WSJF_HFFS_H 37 | -------------------------------------------------------------------------------- /scheduler/benchmark/server/policies/scheduler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_BENCHMARK_SERVER_POLICIES_SCHEDULER_HPP 2 | #define SCHEDULER_BENCHMARK_SERVER_POLICIES_SCHEDULER_HPP 3 | 4 | // STD headers 5 | #include 6 | #include 7 | 8 | // DPDK headers 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // DPDK-related macros 18 | #define BURST_SIZE 32 19 | #define MBUF_CACHE_SIZE 512 20 | #define MIN_NUM_MBUFS 8192 21 | #define DESC_RING_SIZE 1024 22 | #define SCHEDULER_QUEUE_SIZE 8192 23 | #define PROCESS_RING_SIZE (BURST_SIZE) 24 | 25 | /** 26 | * Represents a packet scheduler. 27 | */ 28 | template 29 | class Scheduler { 30 | private: 31 | Policy policy_; 32 | uint64_t num_total_rx_ = 0; 33 | struct rte_mempool* mbuf_pool_; 34 | 35 | public: 36 | Scheduler(struct rte_mempool* pool, struct rte_ring* pr) : 37 | policy_(pool, pr), mbuf_pool_(pool) {} 38 | 39 | void run(volatile bool *quit) { 40 | if (rte_eth_dev_socket_id(0) > 0 && 41 | rte_eth_dev_socket_id(0) != (int) rte_socket_id()) { 42 | std::cout << "[Scheduler] WARNING, port 0 is on remote " 43 | << "NUMA node to RX thread. Performance will " 44 | << "not be optimal." << std::endl; 45 | } 46 | std::cout << "[Scheduler] Policy: " 47 | << policy_.name() << std::endl; 48 | 49 | struct rte_mbuf *bufs[BURST_SIZE]; 50 | while (likely(!(*quit))) { 51 | // Schedule a burst of packets 52 | policy_.scheduleBurst(); 53 | 54 | // Fetch a burst of RX packets and push them onto the packet queue 55 | const uint16_t num_rx = rte_eth_rx_burst(0, 0, bufs, BURST_SIZE); 56 | if (likely(num_rx != 0)) { 57 | num_total_rx_ += num_rx; 58 | policy_.enqueueBurst(bufs, num_rx); 59 | } 60 | } 61 | } 62 | }; 63 | 64 | #endif // SCHEDULER_BENCHMARK_SERVER_POLICIES_SCHEDULER_HPP 65 | -------------------------------------------------------------------------------- /scheduler/cmake/Finddpdk.cmake: -------------------------------------------------------------------------------- 1 | # Try to find dpdk 2 | # 3 | # Once done, this will define 4 | # 5 | # dpdk::dpdk 6 | # dpdk_FOUND 7 | # dpdk_INCLUDE_DIR 8 | # dpdk_LIBRARIES 9 | 10 | find_package(PkgConfig QUIET) 11 | if(PKG_CONFIG_FOUND) 12 | pkg_check_modules(dpdk QUIET libdpdk) 13 | endif() 14 | 15 | if(dpdk_INCLUDE_DIRS) 16 | # good 17 | elseif(TARGET dpdk::dpdk) 18 | get_target_property(dpdk_INCLUDE_DIRS 19 | dpdk::dpdk INTERFACE_INCLUDE_DIRECTORIES) 20 | else() 21 | find_path(dpdk_config_INCLUDE_DIR rte_config.h 22 | HINTS 23 | ENV DPDK_DIR 24 | PATH_SUFFIXES 25 | dpdk 26 | include) 27 | find_path(dpdk_common_INCLUDE_DIR rte_common.h 28 | HINTS 29 | ENC DPDK_DIR 30 | PATH_SUFFIXES 31 | dpdk 32 | include) 33 | set(dpdk_INCLUDE_DIRS "${dpdk_config_INCLUDE_DIR}") 34 | if(NOT dpdk_config_INCLUDE_DIR EQUAL dpdk_common_INCLUDE_DIR) 35 | list(APPEND dpdk_INCLUDE_DIRS "${dpdk_common_INCLUDE_DIR}") 36 | endif() 37 | endif() 38 | 39 | set(components 40 | bus_pci 41 | bus_vdev 42 | cfgfile 43 | cmdline 44 | eal 45 | ethdev 46 | hash 47 | kvargs 48 | mbuf 49 | mempool 50 | mempool_ring 51 | mempool_stack 52 | net 53 | pci 54 | pmd_af_packet 55 | pmd_bnxt 56 | pmd_bond 57 | pmd_cxgbe 58 | pmd_e1000 59 | pmd_ena 60 | pmd_enic 61 | pmd_i40e 62 | pmd_ixgbe 63 | pmd_mlx5 64 | pmd_nfp 65 | pmd_qede 66 | pmd_ring 67 | pmd_sfc_efx 68 | pmd_vmxnet3_uio 69 | ring 70 | timer) 71 | 72 | # for collecting dpdk library targets, it will be used when defining dpdk::dpdk 73 | set(_dpdk_libs) 74 | # for list of dpdk library archive paths 75 | set(dpdk_LIBRARIES) 76 | 77 | foreach(c ${components}) 78 | set(dpdk_lib dpdk::${c}) 79 | if(TARGET ${dpdk_lib}) 80 | get_target_property(DPDK_rte_${c}_LIBRARY 81 | ${dpdk_lib} IMPORTED_LOCATION) 82 | else() 83 | find_library(DPDK_rte_${c}_LIBRARY rte_${c} 84 | HINTS 85 | ENV DPDK_DIR 86 | ${dpdk_LIBRARY_DIRS} 87 | PATH_SUFFIXES lib) 88 | endif() 89 | if(DPDK_rte_${c}_LIBRARY) 90 | if (NOT TARGET ${dpdk_lib}) 91 | add_library(${dpdk_lib} UNKNOWN IMPORTED) 92 | set_target_properties(${dpdk_lib} PROPERTIES 93 | INTERFACE_INCLUDE_DIRECTORIES "${dpdk_INCLUDE_DIRS}" 94 | IMPORTED_LOCATION "${DPDK_rte_${c}_LIBRARY}") 95 | if(c STREQUAL pmd_mlx5) 96 | find_package(verbs QUIET) 97 | if(verbs_FOUND) 98 | target_link_libraries(${dpdk_lib} INTERFACE IBVerbs::verbs) 99 | endif() 100 | endif() 101 | endif() 102 | list(APPEND _dpdk_libs ${dpdk_lib}) 103 | list(APPEND dpdk_LIBRARIES ${DPDK_rte_${c}_LIBRARY}) 104 | endif() 105 | endforeach() 106 | 107 | mark_as_advanced(dpdk_INCLUDE_DIRS ${dpdk_LIBRARIES}) 108 | 109 | include(FindPackageHandleStandardArgs) 110 | find_package_handle_standard_args(dpdk DEFAULT_MSG 111 | dpdk_INCLUDE_DIRS 112 | dpdk_LIBRARIES) 113 | 114 | if(dpdk_FOUND) 115 | if(NOT TARGET dpdk::cflags) 116 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64") 117 | set(rte_cflags "-march=core2") 118 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM") 119 | set(rte_cflags "-march=armv7-a") 120 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64") 121 | set(rte_cflags "-march=armv8-a+crc") 122 | endif() 123 | add_library(dpdk::cflags INTERFACE IMPORTED) 124 | if (rte_cflags) 125 | set_target_properties(dpdk::cflags PROPERTIES 126 | INTERFACE_COMPILE_OPTIONS "${rte_cflags}") 127 | endif() 128 | endif() 129 | 130 | if(NOT TARGET dpdk::dpdk) 131 | add_library(dpdk::dpdk INTERFACE IMPORTED) 132 | find_package(Threads QUIET) 133 | list(APPEND _dpdk_libs 134 | Threads::Threads 135 | dpdk::cflags) 136 | set_target_properties(dpdk::dpdk PROPERTIES 137 | INTERFACE_LINK_LIBRARIES "${_dpdk_libs}" 138 | INTERFACE_INCLUDE_DIRECTORIES "${dpdk_INCLUDE_DIRS}") 139 | endif() 140 | endif() 141 | 142 | unset(_dpdk_libs) -------------------------------------------------------------------------------- /scheduler/heaps/binomial_heap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_BINOMIAL_HEAP_HPP 2 | #define SCHEDULER_HEAPS_BINOMIAL_HEAP_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | 11 | // Boost headers 12 | #include 13 | 14 | /** 15 | * Implements a min-heap using a Binomial heap. 16 | */ 17 | template class BinomialHeap final { 18 | private: 19 | // Typedefs 20 | using QueueEntry = MinHeapEntry; 21 | boost::heap::binomial_heap queue_; 22 | 23 | public: 24 | /** 25 | * Returns the current queue size. 26 | */ 27 | size_t size() const { return queue_.size(); } 28 | 29 | /** 30 | * Returns whether the queue is empty. 31 | */ 32 | size_t empty() const { return queue_.empty(); } 33 | 34 | /** 35 | * Returns (w/o popping) the element tag at the front of the queue. 36 | * @throw runtime error if the queue is currently empty. 37 | */ 38 | Tag peek() const { 39 | if (UNLIKELY(queue_.empty())) { 40 | throw std::runtime_error("Cannot peek an empty queue."); 41 | } 42 | return queue_.top().tag(); 43 | } 44 | 45 | /** 46 | * Pops (and returns) the element tag at the front of the queue. 47 | * @throw runtime error if the queue is currently empty. 48 | */ 49 | Tag pop() { 50 | if (UNLIKELY(queue_.empty())) { 51 | throw std::runtime_error("Cannot pop an empty queue."); 52 | } 53 | QueueEntry entry = queue_.top(); // Fetch the highest priority entry 54 | queue_.pop(); // Pop the queue 55 | return entry.tag(); 56 | } 57 | 58 | /** 59 | * Pushes a new entry onto the queue. 60 | */ 61 | void push(const Tag tag, const double weight) { 62 | QueueEntry queue_entry(tag, weight); 63 | queue_.push(queue_entry); 64 | } 65 | }; 66 | 67 | #endif // SCHEDULER_HEAPS_BINOMIAL_HEAP_HPP -------------------------------------------------------------------------------- /scheduler/heaps/bounded_heap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_BOUNDED_HEAP_HPP 2 | #define SCHEDULER_HEAPS_BOUNDED_HEAP_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | 7 | // STD headers 8 | #include 9 | 10 | // Boost headers 11 | #include 12 | 13 | /** 14 | * Represents a heap entry. 15 | */ 16 | template 17 | class BoundedHeapEntry { 18 | private: 19 | // Typedefs. TODO(natre): This is a hack. Figure out 20 | // how to templatize handle_t types in a generic way. 21 | using handle_t = typename boost::heap::fibonacci_heap 22 | >::handle_type; 23 | 24 | Tag tag_; // Tag corresponding to this entry 25 | Metric primary_metric_; // The primary priority metric 26 | handle_t handle_; // Housekeeping: handle to a heap entry 27 | 28 | public: 29 | explicit BoundedHeapEntry(const Tag& tag, const Metric& metric) : 30 | tag_(tag), primary_metric_(metric), handle_() {} 31 | // Accessors 32 | const Tag& tag() const { return tag_; } 33 | handle_t getHandle() const { return handle_; } 34 | Metric getPrimaryMetric() const { return primary_metric_; } 35 | 36 | // Mutators 37 | void setHandle(handle_t handle) { handle_ = handle; } 38 | 39 | // Comparator 40 | bool operator<(const BoundedHeapEntry& other) const { 41 | if (IsMin) { 42 | return primary_metric_ > other.primary_metric_; 43 | } 44 | else { 45 | return primary_metric_ < other.primary_metric_; 46 | } 47 | } 48 | }; 49 | 50 | /** 51 | * Implements a bounded min-heap using a pair of Fibonacci heaps. 52 | */ 53 | template class BoundedHeap { 54 | private: 55 | // Typedefs 56 | using QueueEntry = BoundedHeapEntry; 57 | using DropQueueEntry = BoundedHeapEntry; 58 | 59 | // Maximum queue size 60 | const size_t kMaxQueueSize; 61 | 62 | // Heap-based queue implementations. We maintain two queues: 63 | // "queue_" which implements the actual WSJF queue and whose 64 | // head represents the element with the lowest job-to-packet 65 | // size ratio, and "drop_queue_", whose head represents the 66 | // lowest-priority element to drop next (if required). This 67 | // is implemented using a max-heap, with the invariant that 68 | // the heaps are consistent (have the same size and set of 69 | // entries) and have a mutually opposite ordering. 70 | boost::heap::fibonacci_heap queue_; 71 | boost::heap::fibonacci_heap drop_queue_; 72 | 73 | public: 74 | explicit BoundedHeap(const size_t max_size) : 75 | kMaxQueueSize(max_size) {} 76 | /** 77 | * Returns the current queue size. 78 | */ 79 | size_t size() const { return queue_.size(); } 80 | 81 | /** 82 | * Returns whether the queue is empty. 83 | */ 84 | size_t empty() const { return queue_.empty(); } 85 | 86 | /** 87 | * Returns (w/o popping) the element tag at the front of the queue. 88 | * @throw runtime error if the queue is currently empty. 89 | */ 90 | Tag peek() const { 91 | if (UNLIKELY(queue_.empty())) { 92 | throw std::runtime_error("Cannot peek an empty queue."); 93 | } 94 | return queue_.top().tag(); 95 | } 96 | 97 | /** 98 | * Pops (and returns) the element tag at the front of the queue. 99 | * @throw runtime error if the queue is currently empty. 100 | */ 101 | Tag pop() { 102 | if (UNLIKELY(queue_.empty())) { 103 | throw std::runtime_error("Cannot pop an empty queue."); 104 | } 105 | QueueEntry entry = queue_.top(); // Fetch the highest priority entry 106 | drop_queue_.erase(entry.getHandle()); // Update the drop queue 107 | queue_.pop(); // Pop the queue 108 | 109 | SP_ASSERT(LIKELY(drop_queue_.size() == queue_.size())); 110 | return entry.tag(); 111 | } 112 | 113 | /** 114 | * Pushes a new entry onto the queue. If the queue is at capacity, 115 | * also removes and returns the lowest-priority entry. Note: This 116 | * may be the parameterized entry itself. 117 | */ 118 | bool push(const Tag tag, const double weight, Tag& erased_tag) { 119 | // Instantiate the queue entries. 120 | QueueEntry queue_entry(tag, weight); 121 | DropQueueEntry drop_queue_entry(tag, weight); 122 | 123 | // Push them onto the respective queues 124 | auto queue_handle = queue_.push(queue_entry); 125 | auto drop_queue_handle = drop_queue_.push(drop_queue_entry); 126 | 127 | // Update the handles 128 | (*queue_handle).setHandle(drop_queue_handle); 129 | (*drop_queue_handle).setHandle(queue_handle); 130 | 131 | // If required, pop the lowest-priority element 132 | if (queue_.size() > kMaxQueueSize) { 133 | DropQueueEntry erased_entry = drop_queue_.top(); 134 | queue_.erase(erased_entry.getHandle()); 135 | drop_queue_.pop(); 136 | 137 | // Update the return value (erased entry tag) 138 | erased_tag = erased_entry.tag(); 139 | return true; 140 | } 141 | SP_ASSERT(LIKELY(drop_queue_.size() == queue_.size())); 142 | return false; 143 | } 144 | }; 145 | 146 | #endif // SCHEDULER_HEAPS_BOUNDED_HEAP_HPP 147 | -------------------------------------------------------------------------------- /scheduler/heaps/fcfs_queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_FCFS_QUEUE_HPP 2 | #define SCHEDULER_HEAPS_FCFS_QUEUE_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | #include 11 | 12 | /** 13 | * Represents a basic FCFS queue. 14 | */ 15 | template class FCFSQueue { 16 | private: 17 | std::queue queue_; 18 | 19 | public: 20 | /** 21 | * Returns the current queue size. 22 | */ 23 | size_t size() const { return queue_.size(); } 24 | 25 | /** 26 | * Returns whether the queue is empty. 27 | */ 28 | size_t empty() const { return queue_.empty(); } 29 | 30 | /** 31 | * Returns (w/o popping) the element tag at the front of the queue. 32 | * @throw runtime error if the queue is currently empty. 33 | */ 34 | Tag peek() const { 35 | if (UNLIKELY(queue_.empty())) { 36 | throw std::runtime_error("Cannot peek an empty queue."); 37 | } 38 | return queue_.front(); 39 | } 40 | 41 | /** 42 | * Pops (and returns) the element tag at the front of the queue. 43 | * @throw runtime error if the queue is currently empty. 44 | */ 45 | Tag pop() { 46 | if (UNLIKELY(queue_.empty())) { 47 | throw std::runtime_error("Cannot pop an empty queue."); 48 | } 49 | Tag entry = queue_.front(); // Fetch the head entry 50 | queue_.pop(); // Pop the queue 51 | return entry; 52 | } 53 | 54 | /** 55 | * Pushes a new entry onto the queue. 56 | */ 57 | void push(const Tag tag, const double weight) { 58 | SUPPRESS_UNUSED_WARNING(weight); 59 | queue_.push(tag); 60 | } 61 | }; 62 | 63 | #endif // SCHEDULER_HEAPS_FCFS_QUEUE_HPP -------------------------------------------------------------------------------- /scheduler/heaps/fibonacci_heap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_FIBONACCI_HEAP_HPP 2 | #define SCHEDULER_HEAPS_FIBONACCI_HEAP_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | 11 | // Boost headers 12 | #include 13 | 14 | /** 15 | * * Implements a min-heap using a Fibonacci heap. 16 | */ 17 | template class FibonacciHeap { 18 | private: 19 | // Typedefs 20 | using QueueEntry = MinHeapEntry; 21 | boost::heap::fibonacci_heap queue_; 22 | 23 | public: 24 | /** 25 | * Returns the current queue size. 26 | */ 27 | size_t size() const { return queue_.size(); } 28 | 29 | /** 30 | * Returns whether the queue is empty. 31 | */ 32 | size_t empty() const { return queue_.empty(); } 33 | 34 | /** 35 | * Returns (w/o popping) the element tag at the front of the queue. 36 | * @throw runtime error if the queue is currently empty. 37 | */ 38 | Tag peek() const { 39 | if (UNLIKELY(queue_.empty())) { 40 | throw std::runtime_error("Cannot peek an empty queue."); 41 | } 42 | return queue_.top().tag(); 43 | } 44 | 45 | /** 46 | * Pops (and returns) the element tag at the front of the queue. 47 | * @throw runtime error if the queue is currently empty. 48 | */ 49 | Tag pop() { 50 | if (UNLIKELY(queue_.empty())) { 51 | throw std::runtime_error("Cannot pop an empty queue."); 52 | } 53 | QueueEntry entry = queue_.top(); // Fetch the highest priority entry 54 | queue_.pop(); // Pop the queue 55 | return entry.tag(); 56 | } 57 | 58 | /** 59 | * Pushes a new entry onto the queue. 60 | */ 61 | void push(const Tag tag, const double weight) { 62 | QueueEntry queue_entry(tag, weight); 63 | queue_.push(queue_entry); 64 | } 65 | }; 66 | 67 | #endif // SCHEDULER_HEAPS_FIBONACCI_HEAP_HPP 68 | -------------------------------------------------------------------------------- /scheduler/heaps/hffs_queue/hardware/ffs.sv: -------------------------------------------------------------------------------- 1 | /* 2 | * Find first-set bits (MSB, LSB). 3 | */ 4 | module ffs #( 5 | parameter WIDTH_LOG = 4, 6 | localparam WIDTH = (1 << WIDTH_LOG) 7 | ) ( 8 | input logic [WIDTH-1:0] x, 9 | output logic [WIDTH_LOG-1:0] msb, 10 | output logic [WIDTH_LOG-1:0] lsb, 11 | output logic zero 12 | ); 13 | 14 | integer i, width; 15 | logic [WIDTH-1:0] y; 16 | logic [WIDTH-1:0] part_msb; 17 | logic [WIDTH-1:0] part_lsb; 18 | 19 | // Zero input? 20 | assign zero = (x == 0); 21 | 22 | // Leading one (MSB) detector 23 | always @(*) begin 24 | msb = 0; 25 | part_msb = x; 26 | for (i = (WIDTH_LOG-1); i >= 0; i = i - 1) begin 27 | width = 1 << i; 28 | if (|(part_msb >> width)) begin 29 | msb[i] = 1; 30 | end 31 | part_msb = msb[i] ? (part_msb >> width) : 32 | (part_msb & ((1'd1 << width) - 1'd1)); 33 | end 34 | end 35 | 36 | // Reverse bit order for LSB detection 37 | always @(*) begin 38 | for (i = (WIDTH-1); i >= 0; i = i - 1) begin 39 | y[i] = x[(WIDTH-1) - i]; 40 | end 41 | end 42 | 43 | // Trailing one (LSB) detector 44 | // TODO(natre): Optimize impl. 45 | always @(*) begin 46 | lsb = 0; 47 | part_lsb = y; 48 | for (i = (WIDTH_LOG-1); i >= 0; i = i - 1) begin 49 | width = 1 << i; 50 | if (|(part_lsb >> width)) begin 51 | lsb[i] = 1; 52 | end 53 | part_lsb = lsb[i] ? (part_lsb >> width) : 54 | (part_lsb & ((1'd1 << width) - 1'd1)); 55 | end 56 | lsb = ~lsb; 57 | end 58 | 59 | endmodule 60 | -------------------------------------------------------------------------------- /scheduler/heaps/hffs_queue/hardware/heap_ops_pkg.sv: -------------------------------------------------------------------------------- 1 | package heap_ops_pkg; 2 | 3 | typedef enum logic [1:0] { 4 | HEAP_OP_ENQUE = 0, 5 | HEAP_OP_DEQUE_MIN, 6 | HEAP_OP_DEQUE_MAX 7 | } heap_op_t; 8 | 9 | endpackage 10 | -------------------------------------------------------------------------------- /scheduler/heaps/hffs_queue/software/hffs_queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_HFFS_QUEUE_HPP 2 | #define SCHEDULER_HEAPS_HFFS_QUEUE_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | #include 11 | #include 12 | 13 | /** 14 | * Represents an approximate min-heap, implemented 15 | * using a Hierarchical Find First Set (FFS) Queue. 16 | */ 17 | template 18 | class HierarchicalFindFirstSetQueue { 19 | private: 20 | /** 21 | * Memoized level state. 22 | */ 23 | struct LevelState { 24 | uint32_t bitmap_idx = 0; 25 | uint32_t nonzero_bit_idx = 0; 26 | }; 27 | 28 | // Queue parameters 29 | Weight kScaleFactor = 0; 30 | uint32_t kNumLevels = 0; 31 | uint32_t kNumBitmaps = 0; 32 | const uint32_t kNumBuckets = 0; 33 | 34 | // Housekeeping 35 | uint32_t size_ = 0; 36 | uint32_t* bitmaps_ = nullptr; 37 | std::vector level_offsets_; 38 | std::vector level_stack_; 39 | std::vector> buckets_; 40 | 41 | /** 42 | * Initializes the queue state. 43 | */ 44 | void init() { 45 | kNumLevels = 1; kNumBitmaps = 1; 46 | uint32_t current_buckets = 32; 47 | level_offsets_.push_back(0); 48 | 49 | // Compute the number of levels and total number of 50 | // bitmaps required to represent the bucket queue. 51 | while (kNumBuckets > current_buckets) { 52 | level_offsets_.push_back(kNumBitmaps); 53 | kNumBitmaps += current_buckets; 54 | current_buckets *= 32; 55 | kNumLevels++; 56 | } 57 | // Allocate the buckets, bitmap tree, and level stack 58 | bitmaps_ = new uint32_t[kNumBitmaps]{}; 59 | level_stack_.resize(kNumLevels); 60 | buckets_.resize(kNumBuckets); 61 | } 62 | 63 | public: 64 | explicit HierarchicalFindFirstSetQueue( 65 | const uint32_t num_buckets, const Weight scale_factor) : 66 | kScaleFactor(scale_factor), kNumBuckets(num_buckets) { 67 | init(); // Init the queue state 68 | } 69 | ~HierarchicalFindFirstSetQueue() { delete[] bitmaps_; } 70 | 71 | /** 72 | * Unscaled weight parameters. 73 | */ 74 | struct UnscaledWeight { 75 | Weight numerator; 76 | Weight denominator; 77 | }; 78 | 79 | /** 80 | * Returns the current queue size. 81 | */ 82 | size_t size() const { return size_; } 83 | 84 | /** 85 | * Returns whether the queue is empty. 86 | */ 87 | size_t empty() const { return (size_ == 0); } 88 | 89 | /** 90 | * Internal helper method. Pops (and returns) the tag 91 | * corresponding to the {min, max} element in the queue. 92 | * @throw runtime error if the queue is currently empty. 93 | */ 94 | template 95 | Tag pop_() { 96 | if (UNLIKELY(empty())) { 97 | throw std::runtime_error("Cannot pop an empty queue."); 98 | } 99 | uint32_t intralevel_bitmap_idx = 0; 100 | for (int idx = 0; idx < (int) kNumLevels; idx++) { 101 | uint32_t bitmap_idx = (level_offsets_[idx] + 102 | intralevel_bitmap_idx); 103 | // Sanity check 104 | SP_ASSERT(LIKELY(bitmaps_[bitmap_idx] != 0)); 105 | 106 | // Memoize the bitmap and bit indices for this level 107 | int bit_idx; 108 | if (IsPopMin) { 109 | bit_idx = __builtin_ctz(bitmaps_[bitmap_idx]); 110 | } 111 | else { 112 | bit_idx = ((sizeof(*bitmaps_) * 8 - 1) - 113 | __builtin_clz(bitmaps_[bitmap_idx])); 114 | } 115 | level_stack_[idx].nonzero_bit_idx = bit_idx; 116 | level_stack_[idx].bitmap_idx = bitmap_idx; 117 | 118 | // Update the intralevel bitmap index 119 | intralevel_bitmap_idx = ( 120 | (intralevel_bitmap_idx * 32) + bit_idx); 121 | } 122 | // Pop the corresponding bucket 123 | uint32_t bucket_idx = intralevel_bitmap_idx; // Actual bucket 124 | SP_ASSERT(LIKELY(!buckets_[bucket_idx].empty())); 125 | Tag entry = buckets_[bucket_idx].front(); 126 | buckets_[bucket_idx].pop_front(); 127 | 128 | // If this bucket becomes empty, update the bitmap tree 129 | bool update = buckets_[bucket_idx].empty(); 130 | for (int idx = (kNumLevels - 1); idx >= 0 && update; idx--) { 131 | uint32_t mask = ~(1UL << level_stack_[idx].nonzero_bit_idx); 132 | uint32_t bitmap_idx = level_stack_[idx].bitmap_idx; 133 | bitmaps_[bitmap_idx] &= mask; // Apply the mask 134 | update = (bitmaps_[bitmap_idx] == 0); 135 | } 136 | size_--; // Update queue size 137 | return entry; 138 | } 139 | 140 | /** 141 | * Pops (and returns) the tag corresponding to the min element. 142 | * @throw runtime error if the queue is currently empty. 143 | */ 144 | inline Tag popMin() { return pop_(); } 145 | 146 | /** 147 | * Pops (and returns) the tag corresponding to the max element. 148 | * @throw runtime error if the queue is currently empty. 149 | */ 150 | inline Tag popMax() { return pop_(); } 151 | 152 | /** 153 | * Pushes a new entry onto the queue. 154 | */ 155 | void push(const Tag tag, const UnscaledWeight weight) { 156 | // Insert the given entry into the corresponding bucket 157 | uint32_t bucket_idx = uint32_t( 158 | (weight.numerator * kScaleFactor) / weight.denominator); 159 | 160 | // Sanity check 161 | SP_ASSERT(LIKELY(bucket_idx < kNumBuckets)); 162 | bool update = buckets_[bucket_idx].empty(); 163 | buckets_[bucket_idx].push_back(tag); 164 | 165 | // Update the bitmap sub-tree 166 | int bit_idx = (bucket_idx & 0x1F); 167 | uint32_t intralevel_bitmap_idx = (bucket_idx / 32); 168 | for (int idx = (kNumLevels - 1); idx >= 0 && update; idx--) { 169 | uint32_t bitmap_idx = (level_offsets_[idx] + 170 | intralevel_bitmap_idx); 171 | // Apply the bitmask 172 | update = (bitmaps_[bitmap_idx] == 0); 173 | uint32_t mask = (1UL << bit_idx); 174 | bitmaps_[bitmap_idx] |= mask; 175 | 176 | // Compute the indices for the next level 177 | bit_idx = (intralevel_bitmap_idx & 0x1F); 178 | intralevel_bitmap_idx /= 32; 179 | } 180 | // Update queue size 181 | size_++; 182 | } 183 | }; 184 | 185 | #endif // SCHEDULER_HEAPS_HFFS_QUEUE_HPP 186 | -------------------------------------------------------------------------------- /scheduler/heaps/priority_queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_HEAPS_PRIORITY_QUEUE_HPP 2 | #define SCHEDULER_HEAPS_PRIORITY_QUEUE_HPP 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | 8 | // STD headers 9 | #include 10 | 11 | // Boost headers 12 | #include 13 | 14 | /** 15 | * Implements a min-heap using a priority queue. 16 | */ 17 | template class PriorityQueue { 18 | private: 19 | // Typedefs 20 | using QueueEntry = MinHeapEntry; 21 | boost::heap::priority_queue queue_; 22 | 23 | public: 24 | /** 25 | * Returns the current queue size. 26 | */ 27 | size_t size() const { return queue_.size(); } 28 | 29 | /** 30 | * Returns whether the queue is empty. 31 | */ 32 | size_t empty() const { return queue_.empty(); } 33 | 34 | /** 35 | * Returns (w/o popping) the element tag at the front of the queue. 36 | * @throw runtime error if the queue is currently empty. 37 | */ 38 | Tag peek() const { 39 | if (UNLIKELY(queue_.empty())) { 40 | throw std::runtime_error("Cannot peek an empty queue."); 41 | } 42 | return queue_.top().tag(); 43 | } 44 | 45 | /** 46 | * Pops (and returns) the element tag at the front of the queue. 47 | * @throw runtime error if the queue is currently empty. 48 | */ 49 | Tag pop() { 50 | if (UNLIKELY(queue_.empty())) { 51 | throw std::runtime_error("Cannot pop an empty queue."); 52 | } 53 | QueueEntry entry = queue_.top(); // Fetch the highest priority entry 54 | queue_.pop(); // Pop the queue 55 | return entry.tag(); 56 | } 57 | 58 | /** 59 | * Pushes a new entry onto the queue. 60 | */ 61 | void push(const Tag tag, const double weight) { 62 | QueueEntry queue_entry(tag, weight); 63 | queue_.push(queue_entry); 64 | } 65 | }; 66 | 67 | #endif // SCHEDULER_HEAPS_PRIORITY_QUEUE_HPP 68 | -------------------------------------------------------------------------------- /simulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Sources 2 | add_subdirectory(src) 3 | -------------------------------------------------------------------------------- /simulator/configs/examples/example_1.cfg: -------------------------------------------------------------------------------- 1 | policy = "sjf"; # Use SJF scheduling 2 | max_num_arrivals = 1000000; # Maximum arrival count for simulation 3 | 4 | application = 5 | { 6 | type = "iid_job_sizes"; # Job sizes sampled IID from a dist 7 | stsf = 1; # Service-time scaling factor 8 | max_attack_job_size_ns = 1000000; # Max attack job size: 1 ms 9 | 10 | job_size_ns_dist = # Job size distribution 11 | { 12 | type = "normal"; # Truncated normal distribution 13 | mu = 4000; # Average job size: 4 us 14 | sigma = 500; # Stddev in job size: 500 ns 15 | min = 0; # Minimum job size: 0 ns 16 | max = 10000; # Maximum job size: 10 us 17 | } 18 | }; 19 | 20 | # Innocent workload 21 | innocent_traffic = 22 | { 23 | type = "synthetic"; # Synthetic traffic 24 | num_flows = 100; # Number of innocent flows 25 | 26 | packet_size_bits_dist = # Packet size distribution 27 | { 28 | type = "normal"; # Truncated normal distribution 29 | mu = 4000; # Average packet size: 4000 bits 30 | sigma = 400; # Stddev in packet size: 400 bits 31 | min = 512; # Minimum packet size: 512 bits (64B) 32 | max = 12144; # Maximum packet size: 12 Kbits (1518B) 33 | } 34 | average_packet_size_bits = 4000; # Average packet size (bits) 35 | rate_bps = 700000000; # Innocent traffic rate (700 Mbps) 36 | }; 37 | 38 | # Attack workload 39 | attack_traffic = 40 | { 41 | type = "synthetic"; # Synthetic traffic 42 | num_flows = 1; # Single attack flow 43 | job_size_ns = 4200; # Attack jobs size: 4.2 us (arbitrary) 44 | packet_size_bits = 512; # Min-sized attack packets 45 | rate_bps = 100000000; # Attack traffic rate (100 Mbps) 46 | }; 47 | -------------------------------------------------------------------------------- /simulator/configs/templates/full_matcher.cfg: -------------------------------------------------------------------------------- 1 | application = 2 | { 3 | type = "echo"; # Echoes job sizes in trace 4 | stsf = 1; 5 | max_attack_job_size_ns = 62670; # Max attack job size in ns. The 63 us value corresponds 6 | # to the maximum time the Pigasus Full Matcher spends on 7 | # an *innocent* packet for the workloads we've evaluated; 8 | # a real attack should be able to do even better (making 9 | # both FCFS and FQ perform worse). 10 | }; 11 | 12 | innocent_traffic = 13 | { 14 | type = "trace"; 15 | trace_fp = "${INSERT_CSV_TRACE_FILE_PATH_HERE}"; 16 | }; 17 | -------------------------------------------------------------------------------- /simulator/configs/templates/iid_job_sizes.cfg: -------------------------------------------------------------------------------- 1 | max_num_arrivals = 1000000; 2 | 3 | application = 4 | { 5 | type = "iid_job_sizes"; 6 | stsf = 1; 7 | max_attack_job_size_ns = 1000000; 8 | 9 | job_size_ns_dist = 10 | { 11 | type = "normal"; 12 | mu = 4000; 13 | sigma = 500; 14 | min = 0; 15 | max = 10000; 16 | } 17 | }; 18 | 19 | innocent_traffic = 20 | { 21 | type = "synthetic"; 22 | num_flows = 100; 23 | 24 | packet_size_bits_dist = 25 | { 26 | type = "normal"; 27 | mu = 4000; 28 | sigma = 400; 29 | min = 512; 30 | max = 12144; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /simulator/configs/templates/tcp_reassembly.cfg: -------------------------------------------------------------------------------- 1 | application = 2 | { 3 | type = "tcp_reassembly"; # Simulates FPGA-based TCP reassembly 4 | stsf = 5; # 1 FPGA cycle = 5 ns 5 | max_attack_job_size_ns = 32768; # Max attack job size in ns (~6.4K cycles) 6 | }; 7 | 8 | innocent_traffic = 9 | { 10 | type = "trace"; 11 | trace_fp = "${INSERT_CSV_TRACE_FILE_PATH_HERE}"; 12 | }; 13 | -------------------------------------------------------------------------------- /simulator/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmu-snap/SurgeProtector/b789d660f86d0ea41ed43d53cd1a0520379a9883/simulator/scripts/__init__.py -------------------------------------------------------------------------------- /simulator/scripts/adversary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmu-snap/SurgeProtector/b789d660f86d0ea41ed43d53cd1a0520379a9883/simulator/scripts/adversary/__init__.py -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_common.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | import io, libconf 4 | 5 | class SchedulingMetrics: 6 | """Metrics used for scheduling.""" 7 | def __init__(self, psize: int, jsize: float): 8 | self.jsize = jsize # Corresponding job size 9 | self.psize = psize # Corresponding packet size 10 | self.z_ratio = (jsize / psize) # {Job/Packet} size 11 | 12 | class Packet: 13 | """Represents a packet (from an output log).""" 14 | # Constant parameters 15 | MIN_PACKET_SIZE = 512 # 64B 16 | MAX_PACKET_SIZE = 12144 # 1518B 17 | 18 | def __init__(self, arrive_clk: float, depart_clk: float, 19 | flow_id: str, class_tag: str, psize: int, 20 | jsize: float): 21 | 22 | self.arrive_clk = arrive_clk # Arrival clock 23 | self.depart_clk = depart_clk # Departure clock 24 | self.flow_id = flow_id # This packet's flow ID 25 | self.class_tag = class_tag # The traffic class 26 | self.metrics = SchedulingMetrics(psize, jsize) 27 | 28 | @staticmethod 29 | def from_log(line: str) -> Packet: 30 | """Parse a line of packet log (simulator output).""" 31 | values = [x.strip() for x in line.split(";")] 32 | assert len(values) >= 6 # Sanity check 33 | 34 | return Packet(float(values[0]), # Arrival clk 35 | float(values[1]), # Depart clk 36 | values[2], # Flow ID 37 | values[3], # Class tag 38 | int(values[4]), # Packet size 39 | float(values[5])) # Job size (estimate) 40 | 41 | class AttackStrategy: 42 | """Represents an adversary's attack strategy.""" 43 | def __init__(self, estimated_jsize: float, actual_jsize: float, 44 | psize: int, expected_goodput: float, alpha: float): 45 | self.estimated_jsize = estimated_jsize # Estimated job size (ns) 46 | self.actual_jsize = actual_jsize # Actual job size (ns) 47 | self.psize = psize # Packet size (bits) 48 | 49 | self.expected_goodput_gbps = expected_goodput # Theoretical goodput (Gbps) 50 | self.expected_alpha = alpha # Theoretical displacement factor 51 | 52 | class BaseAnalyzer(ABC): 53 | """Abstract base class for analyzing policies 54 | from the perspective of an optimal adversary.""" 55 | def __init__(self): 56 | # Useful top-level statistics 57 | self.r_max = 0 58 | self.num_total_packets = 0 59 | self.average_innocent_psize = 0 60 | self.maximum_innocent_psize = 0 61 | self.average_innocent_jsize = 0 62 | self.maximum_innocent_jsize = 0 63 | 64 | # Config parameters 65 | self.maximum_attack_jsize = 0 66 | 67 | @abstractmethod 68 | def get_policy_name(self) -> str: 69 | """Returns the policy name.""" 70 | raise NotImplementedError 71 | 72 | @abstractmethod 73 | def _analyze_log_packet(self, packet: Packet) -> None: 74 | """Implementation-specific packet analysis.""" 75 | raise NotImplementedError 76 | 77 | @abstractmethod 78 | def get_optimal_strategy( 79 | self, r_I_gbps: float, r_A_gbps: float) -> AttackStrategy: 80 | """Given a configuration, returns a tuple representing 81 | the adversary's optimal strategy for the policy.""" 82 | raise NotImplementedError 83 | 84 | def _analyze_log(self, log_fp: str) -> None: 85 | """Analyzes the given log file.""" 86 | with open(log_fp, 'r') as lines: 87 | for line in lines: 88 | packet = Packet.from_log(line) 89 | psize = packet.metrics.psize 90 | jsize = packet.metrics.jsize 91 | 92 | # Update statistics 93 | self.num_total_packets += 1 94 | self.average_innocent_psize += psize 95 | self.average_innocent_jsize += jsize 96 | self.maximum_innocent_psize = max( 97 | self.maximum_innocent_psize, psize) 98 | 99 | self.maximum_innocent_jsize = max( 100 | self.maximum_innocent_jsize, jsize) 101 | 102 | # Invoke the specific parsing functionality 103 | self._analyze_log_packet(packet) 104 | 105 | # Compute the average packet and job size, and r_Max 106 | self.average_innocent_psize /= self.num_total_packets 107 | self.average_innocent_jsize /= self.num_total_packets 108 | self.r_max = (self.average_innocent_psize / 109 | self.average_innocent_jsize) 110 | 111 | # Convert the packet size to an integer 112 | self.average_innocent_psize = int( 113 | self.average_innocent_psize) 114 | 115 | # Print stats 116 | if (False): 117 | print("Average job size: {:.2f} ns".format( 118 | self.average_innocent_jsize)) 119 | 120 | print("Maximum job size: {} ns".format( 121 | self.maximum_innocent_jsize)) 122 | 123 | print("Average packet size: {} bits".format( 124 | self.average_innocent_psize)) 125 | 126 | print("Maximum packet size: {} bits\n".format( 127 | self.maximum_innocent_psize)) 128 | 129 | def _parse_config(self, config_fp: str) -> None: 130 | """Parses the given configuration file. Note that this 131 | method should always be invoked AFTER analyze_log.""" 132 | with io.open(config_fp) as f: 133 | config = libconf.load(f) 134 | 135 | # The maximum attack job size (ns) 136 | # is specified in the config file. 137 | self.maximum_attack_jsize = (config[ 138 | "application"]["max_attack_job_size_ns"]) 139 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_fcfs.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | class FCFSAnalyzer(common.BaseAnalyzer): 4 | """Performs analysis for FCFS scheduling.""" 5 | def __init__(self, config_fp: str, log_fp: str): 6 | super().__init__() # Init base class 7 | self._analyze_log(log_fp) # Perform analysis 8 | self._parse_config(config_fp) # Parse config file 9 | 10 | @staticmethod 11 | def get_policy_name() -> str: 12 | """Returns the policy name.""" 13 | return "fcfs" 14 | 15 | def _analyze_log_packet(self, packet: common.Packet) -> None: 16 | """Implementation-specific parsing.""" 17 | return 18 | 19 | def get_optimal_strategy( 20 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 21 | """Given a configuration, returns a tuple representing 22 | the adversary's optimal strategy for the policy.""" 23 | total_adversarial_work = ((r_A_gbps / common.Packet.MIN_PACKET_SIZE) * 24 | self.maximum_attack_jsize) 25 | 26 | total_innocent_work = ((r_I_gbps / self.average_innocent_psize) * 27 | self.average_innocent_jsize) 28 | 29 | # Compute the theoretical goodput 30 | expected_goodput = r_I_gbps * min( 31 | 1, 1 / (total_innocent_work + total_adversarial_work)) 32 | 33 | # Compute the theoretical DF 34 | ideal_goodput = min(r_I_gbps, self.r_max) 35 | alpha = (0 if (r_A_gbps == 0) else max(0, 36 | (ideal_goodput - expected_goodput)) / r_A_gbps) 37 | 38 | # Return the optimal strategy 39 | return common.AttackStrategy( 40 | self.maximum_attack_jsize, self.maximum_attack_jsize, 41 | common.Packet.MIN_PACKET_SIZE, expected_goodput, alpha) 42 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_fq.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | class FQAnalyzer(common.BaseAnalyzer): 4 | """Performs analysis for FQ scheduling.""" 5 | def __init__(self, config_fp: str, log_fp: str): 6 | super().__init__() # Init base class 7 | self.flows = set() # Set of flows seen 8 | 9 | self._analyze_log(log_fp) # Perform analysis 10 | self._parse_config(config_fp) # Parse config file 11 | 12 | @staticmethod 13 | def get_policy_name() -> str: 14 | """Returns the policy name.""" 15 | return "fq" 16 | 17 | def _analyze_log_packet(self, packet: common.Packet) -> None: 18 | """Implementation-specific parsing.""" 19 | self.flows.add(packet.flow_id) 20 | 21 | def get_optimal_strategy( 22 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 23 | """Given a configuration, returns a tuple representing 24 | the adversary's optimal strategy for the policy.""" 25 | total_adversarial_work = ((r_A_gbps / common.Packet.MIN_PACKET_SIZE) * 26 | self.maximum_attack_jsize) 27 | 28 | # Compute the theoretical goodput 29 | k = len(self.flows) 30 | expected_goodput = min( 31 | r_I_gbps, (k * self.r_max) / (total_adversarial_work + k)) 32 | 33 | # Compute the theoretical DF 34 | ideal_goodput = min(r_I_gbps, self.r_max) 35 | alpha = (0 if (r_A_gbps == 0) else max(0, 36 | (ideal_goodput - expected_goodput)) / r_A_gbps) 37 | 38 | # Return the optimal strategy 39 | return common.AttackStrategy( 40 | self.maximum_attack_jsize, self.maximum_attack_jsize, 41 | common.Packet.MIN_PACKET_SIZE, expected_goodput, alpha) 42 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_sjf.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | from collections import defaultdict 4 | 5 | class SJFAnalyzer(common.BaseAnalyzer): 6 | """Performs analysis for SJF scheduling.""" 7 | def __init__(self, config_fp: str, log_fp: str): 8 | super().__init__() # Init base class 9 | self.arrivals = [] # List of arrival metrics sorted by jsize 10 | self.jsizes_frequencies = defaultdict(int) # jsize -> frequency 11 | self.jsize_expectations = dict() # jsize -> cumulative expectation 12 | 13 | self._analyze_log(log_fp) # Perform analysis 14 | self._parse_config(config_fp) # Parse config file 15 | self._compute_expectation() # Populate the jsize_expectations map 16 | 17 | @staticmethod 18 | def get_policy_name() -> str: 19 | """Returns the policy name.""" 20 | return "sjf" 21 | 22 | def _analyze_log_packet(self, packet: common.Packet) -> None: 23 | """Implementation-specific parsing.""" 24 | self.arrivals.append(packet.metrics) 25 | self.jsizes_frequencies[int(packet.metrics.jsize)] += 1 26 | 27 | def _compute_expectation(self) -> None: 28 | """Populates the cumulative expectations map.""" 29 | # Sort arrivals in increasing order of job size 30 | self.arrivals.sort(key=lambda x: x.jsize) 31 | 32 | # Compute the pdf and expectation 33 | jsize_cumulative_expectation = 0 34 | for jsize, frequency in sorted(self.jsizes_frequencies.items()): 35 | density = (frequency / self.num_total_packets) 36 | jsize_cumulative_expectation += (density * jsize) 37 | self.jsize_expectations[jsize] = jsize_cumulative_expectation 38 | 39 | def get_optimal_strategy( 40 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 41 | """Given a configuration, returns a tuple representing 42 | the adversary's optimal strategy for the policy.""" 43 | optimal_jsize = self.maximum_innocent_jsize 44 | for jsize, expectation in sorted(self.jsize_expectations.items()): 45 | total_adversarial_work = ((r_A_gbps / common.Packet.MIN_PACKET_SIZE) * 46 | max(0, jsize - 1)) 47 | 48 | total_innocent_work = ((r_I_gbps / self.average_innocent_psize) * 49 | expectation) 50 | 51 | # Current choice of J_{A} pushes the system over capacity 52 | if (total_innocent_work + total_adversarial_work >= 1): 53 | optimal_jsize = (jsize - 1) 54 | break 55 | 56 | # Compute the theoretical goodput 57 | cumulative_scheduled_psize = 0 58 | for arrival_metrics in self.arrivals: 59 | if (arrival_metrics.jsize > optimal_jsize): break 60 | cumulative_scheduled_psize += arrival_metrics.psize 61 | 62 | expected_goodput = ((r_I_gbps * cumulative_scheduled_psize) / 63 | (self.num_total_packets * self.average_innocent_psize)) 64 | 65 | # Compute the theoretical DF 66 | ideal_goodput = min(r_I_gbps, self.r_max) 67 | alpha = (0 if (r_A_gbps == 0) else max(0, 68 | (ideal_goodput - expected_goodput)) / r_A_gbps) 69 | 70 | # Return the optimal strategy 71 | return common.AttackStrategy( 72 | optimal_jsize, optimal_jsize, 73 | common.Packet.MIN_PACKET_SIZE, expected_goodput, alpha) 74 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_sjf_inorder.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | from collections import defaultdict 4 | 5 | class FlowMetrics: 6 | """Flow-level average job and packet sizes.""" 7 | def __init__(self): 8 | self.num_packets = 0 9 | self.total_psize = 0 10 | self.total_jsize = 0 11 | 12 | def add_packet(self, psize: int, jsize: float) -> None: 13 | """Registers a new packet to this flow.""" 14 | self.num_packets += 1 15 | self.total_psize += psize 16 | self.total_jsize += jsize 17 | 18 | def get_average_jsize(self) -> float: 19 | """Returns the average job size for this flow.""" 20 | return (self.total_jsize / self.num_packets) 21 | 22 | class SJFInorderAnalyzer(common.BaseAnalyzer): 23 | """Performs analysis for SJF-Inorder scheduling.""" 24 | def __init__(self, config_fp: str, log_fp: str): 25 | super().__init__() # Init base class 26 | self.flows_sorted = None # List of FlowMetrics sorted by average jsize 27 | self.jsize_expectations = dict() # jsize -> cumulative jsize expectation 28 | self.flow_metrics_map = defaultdict(FlowMetrics) # Flow ID -> FlowMetrics 29 | 30 | self._analyze_log(log_fp) # Perform analysis 31 | self._parse_config(config_fp) # Parse config file 32 | self._compute_expectation() # Populate the jsize_expectations map 33 | 34 | @staticmethod 35 | def get_policy_name() -> str: 36 | """Returns the policy name.""" 37 | return "sjf_inorder" 38 | 39 | def _analyze_log_packet(self, packet: common.Packet) -> None: 40 | """Implementation-specific parsing.""" 41 | # Add the packet to the corresponding flow 42 | self.flow_metrics_map[packet.flow_id].add_packet( 43 | packet.metrics.psize, packet.metrics.jsize) 44 | 45 | def _compute_expectation(self) -> None: 46 | """Populates the cumulative expectations map.""" 47 | # Sort flows in increasing order of average job size 48 | self.flows_sorted = [value for value in sorted( 49 | self.flow_metrics_map.values(), 50 | key=lambda metrics: metrics.get_average_jsize())] 51 | 52 | # Construct dictionary mapping average jsize to the 53 | # expected job size for all flows with that average. 54 | jsize_cumulative_expectation = 0 55 | for flow in self.flows_sorted: 56 | jsize_cumulative_expectation += ( 57 | flow.total_jsize / self.num_total_packets) 58 | 59 | self.jsize_expectations[int( 60 | flow.get_average_jsize())] = jsize_cumulative_expectation 61 | 62 | def get_optimal_strategy( 63 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 64 | """Given a configuration, returns a tuple representing 65 | the adversary's optimal strategy for the policy.""" 66 | optimal_jsize = self.maximum_innocent_jsize 67 | for jsize, expectation in sorted(self.jsize_expectations.items()): 68 | total_adversarial_work = ((r_A_gbps / common.Packet.MIN_PACKET_SIZE) * 69 | max(0, jsize - 1)) 70 | 71 | total_innocent_work = ((r_I_gbps / self.average_innocent_psize) * 72 | expectation) 73 | 74 | # Current choice of J_{A} pushes the system over capacity 75 | if (total_innocent_work + total_adversarial_work >= 1): 76 | optimal_jsize = (jsize - 1) 77 | break 78 | 79 | # Compute the theoretical goodput 80 | cumulative_scheduled_psize = 0 81 | for flow in self.flows_sorted: 82 | flow_jsize = flow.get_average_jsize() 83 | if (flow_jsize > optimal_jsize): break 84 | cumulative_scheduled_psize += flow.total_psize 85 | 86 | expected_goodput = ((r_I_gbps * cumulative_scheduled_psize) / 87 | (self.num_total_packets * self.average_innocent_psize)) 88 | 89 | # Compute the theoretical DF 90 | ideal_goodput = min(r_I_gbps, self.r_max) 91 | alpha = (0 if (r_A_gbps == 0) else max(0, 92 | (ideal_goodput - expected_goodput)) / r_A_gbps) 93 | 94 | # Return the optimal strategy 95 | return common.AttackStrategy( 96 | optimal_jsize, optimal_jsize, 97 | common.Packet.MIN_PACKET_SIZE, expected_goodput, alpha) 98 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_wsjf.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | from fractions import Fraction 4 | import numpy as np 5 | 6 | class WSJFAnalyzer(common.BaseAnalyzer): 7 | """Performs analysis for WSJF scheduling.""" 8 | def __init__(self, config_fp: str, log_fp: str): 9 | super().__init__() # Init base class 10 | self.arrivals = [] # List of arrival metrics sorted by z-ratio 11 | self.zs_expectation_jsizes = None # Cumulative jsize expectation 12 | 13 | # Parameters 14 | self.SCALING_FACTOR = 4096 15 | self.OPTIMAL_ADV_PSIZE = 8192 16 | 17 | # Perform analysis 18 | self._analyze_log(log_fp) # Perform analysis 19 | self._parse_config(config_fp) # Parse config file 20 | self._compute_expectation() # Populate jsize expectations 21 | 22 | @staticmethod 23 | def get_policy_name() -> str: 24 | """Returns the policy name.""" 25 | return "wsjf" 26 | 27 | def _analyze_log_packet(self, packet: common.Packet) -> None: 28 | """Implementation-specific parsing.""" 29 | # Update the arrivals list 30 | self.arrivals.append(packet.metrics) 31 | 32 | def _compute_expectation(self) -> None: 33 | """Populates the cumulative expectations map.""" 34 | # Sort arrivals in increasing order of z 35 | self.arrivals.sort(key=lambda x: x.z_ratio) 36 | 37 | # Compute the max z-ratio 38 | max_z_ratio = self.arrivals[-1].z_ratio 39 | self.zs_expectation_jsizes = np.zeros(round(self.SCALING_FACTOR * 40 | max_z_ratio) + 1) 41 | # Compute the cumulative expectation 42 | jsize_cumulative_expectation = 0 43 | for arrival_metrics in self.arrivals: 44 | jsize_cumulative_expectation += ( 45 | arrival_metrics.jsize / self.num_total_packets) 46 | 47 | # Compute the scaled z-ratio and update the expectation 48 | scaled_z_ratio = round(arrival_metrics.z_ratio * 49 | self.SCALING_FACTOR) 50 | 51 | self.zs_expectation_jsizes[scaled_z_ratio] = ( 52 | jsize_cumulative_expectation) 53 | 54 | def get_optimal_strategy( 55 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 56 | """Given a configuration, returns a tuple representing 57 | the adversary's optimal strategy for the policy.""" 58 | optimal_z = (self.maximum_innocent_jsize / 59 | common.Packet.MIN_PACKET_SIZE) 60 | current_z = 0 61 | for expectation in self.zs_expectation_jsizes: 62 | total_adversarial_work = (r_A_gbps * current_z) 63 | total_innocent_work = ((r_I_gbps / self.average_innocent_psize) * expectation) 64 | 65 | # Current choice of Z_{A} pushes the system over capacity 66 | if (total_innocent_work + total_adversarial_work >= 1): break 67 | optimal_z = current_z 68 | current_z += (1 / self.SCALING_FACTOR) 69 | 70 | # Compute the theoretical goodput 71 | cumulative_scheduled_psize = 0 72 | for arrival_metrics in self.arrivals: 73 | z = arrival_metrics.z_ratio 74 | if (z > optimal_z): break 75 | cumulative_scheduled_psize += arrival_metrics.psize 76 | 77 | expected_goodput = ((r_I_gbps * cumulative_scheduled_psize) / 78 | (self.num_total_packets * self.average_innocent_psize)) 79 | 80 | # Compute the theoretical DF 81 | ideal_goodput = min(r_I_gbps, self.r_max) 82 | alpha = (0 if (r_A_gbps == 0) else max(0, 83 | (ideal_goodput - expected_goodput)) / r_A_gbps) 84 | 85 | # Compute the ratio and rescale the denominator (psize) to be sufficiently large 86 | ratio = Fraction(optimal_z).limit_denominator(common.Packet.MAX_PACKET_SIZE) 87 | (numerator, denominator) = (ratio.numerator, ratio.denominator) 88 | if denominator < self.OPTIMAL_ADV_PSIZE: 89 | numerator *= (self.OPTIMAL_ADV_PSIZE / denominator) 90 | denominator = self.OPTIMAL_ADV_PSIZE 91 | 92 | # Return the optimal strategy 93 | return common.AttackStrategy( 94 | int(numerator), int(numerator), 95 | denominator, expected_goodput, alpha) 96 | -------------------------------------------------------------------------------- /simulator/scripts/adversary/analyze_wsjf_inorder.py: -------------------------------------------------------------------------------- 1 | import adversary.analyze_common as common 2 | 3 | from collections import defaultdict 4 | from fractions import Fraction 5 | import numpy as np 6 | 7 | class FlowMetrics: 8 | """Flow-level average job and packet sizes.""" 9 | def __init__(self): 10 | self.num_packets = 0 11 | self.total_psize = 0 12 | self.total_jsize = 0 13 | 14 | def add_packet(self, psize: int, jsize: float) -> None: 15 | """Registers a new packet to this flow.""" 16 | self.num_packets += 1 17 | self.total_psize += psize 18 | self.total_jsize += jsize 19 | 20 | def get_average_z_ratio(self) -> float: 21 | """Returns the average z-ratio for this flow.""" 22 | return (self.total_jsize / self.total_psize) 23 | 24 | class WSJFInorderAnalyzer(common.BaseAnalyzer): 25 | """Performs analysis for WSJF-Inorder scheduling.""" 26 | def __init__(self, config_fp: str, log_fp: str): 27 | super().__init__() # Init base class 28 | self.flows_sorted = None # List of FlowMetrics sorted by z-ratio 29 | self.zs_expectation_jsizes = None # Cumulative jsize expectation 30 | self.flow_metrics_map = defaultdict(FlowMetrics) # Flow ID -> FlowMetrics 31 | 32 | # Parameters 33 | self.SCALING_FACTOR = 4096 34 | self.OPTIMAL_ADV_PSIZE = 8192 35 | 36 | # Perform analysis 37 | self._analyze_log(log_fp) # Perform analysis 38 | self._parse_config(config_fp) # Parse config file 39 | self._compute_expectation() # Populate jsize expectations 40 | 41 | @staticmethod 42 | def get_policy_name() -> str: 43 | """Returns the policy name.""" 44 | return "wsjf_inorder" 45 | 46 | def _analyze_log_packet(self, packet: common.Packet) -> None: 47 | """Implementation-specific parsing.""" 48 | # Add the packet to the corresponding flow 49 | self.flow_metrics_map[packet.flow_id].add_packet( 50 | packet.metrics.psize, packet.metrics.jsize) 51 | 52 | def _compute_expectation(self) -> None: 53 | """Populates the cumulative expectations map.""" 54 | # Sort flows in increasing order of average job size 55 | self.flows_sorted = [value for value in sorted( 56 | self.flow_metrics_map.values(), 57 | key=lambda metrics: metrics.get_average_z_ratio())] 58 | 59 | # Compute the max z-ratio 60 | max_jp_ratio = self.flows_sorted[-1].get_average_z_ratio() 61 | self.zs_expectation_jsizes = np.zeros(round(self.SCALING_FACTOR * 62 | max_jp_ratio) + 1) 63 | 64 | # Compute the cumulative expectation 65 | jsize_cumulative_expectation = 0 66 | for flow in self.flows_sorted: 67 | jsize_cumulative_expectation += ( 68 | flow.total_jsize / self.num_total_packets) 69 | 70 | # Compute the scaled z-ratio and update the expectation 71 | scaled_jp_ratio = round(flow.get_average_z_ratio() * 72 | self.SCALING_FACTOR) 73 | 74 | self.zs_expectation_jsizes[scaled_jp_ratio] = ( 75 | jsize_cumulative_expectation) 76 | 77 | def get_optimal_strategy( 78 | self, r_I_gbps: float, r_A_gbps: float) -> common.AttackStrategy: 79 | """Given a configuration, returns a tuple representing 80 | the adversary's optimal strategy for the policy.""" 81 | optimal_z = (self.maximum_innocent_jsize / 82 | common.Packet.MIN_PACKET_SIZE) 83 | current_z = 0 84 | for expectation in self.zs_expectation_jsizes: 85 | total_adversarial_work = (r_A_gbps * current_z) 86 | total_innocent_work = ((r_I_gbps / self.average_innocent_psize) * 87 | expectation) 88 | 89 | # Current choice of Z_{A} pushes the system over capacity 90 | if (total_innocent_work + total_adversarial_work >= 1): break 91 | optimal_z = current_z 92 | current_z += (1 / self.SCALING_FACTOR) 93 | 94 | # Compute the theoretical goodput 95 | cumulative_scheduled_psize = 0 96 | for flow in self.flows_sorted: 97 | flow_z = flow.get_average_z_ratio() 98 | if (flow_z > optimal_z): break 99 | cumulative_scheduled_psize += flow.total_psize 100 | 101 | expected_goodput = ((r_I_gbps * cumulative_scheduled_psize) / 102 | (self.num_total_packets * self.average_innocent_psize)) 103 | 104 | # Compute the theoretical DF 105 | ideal_goodput = min(r_I_gbps, self.r_max) 106 | alpha = (0 if (r_A_gbps == 0) else max(0, 107 | (ideal_goodput - expected_goodput)) / r_A_gbps) 108 | 109 | # Compute the ratio and rescale the denominator (psize) to be sufficiently large 110 | ratio = Fraction(optimal_z).limit_denominator(common.Packet.MAX_PACKET_SIZE) 111 | (numerator, denominator) = (ratio.numerator, ratio.denominator) 112 | if denominator < self.OPTIMAL_ADV_PSIZE: 113 | numerator *= (self.OPTIMAL_ADV_PSIZE / denominator) 114 | denominator = self.OPTIMAL_ADV_PSIZE 115 | 116 | # Return the optimal strategy 117 | return common.AttackStrategy( 118 | int(numerator), int(numerator), 119 | denominator, expected_goodput, alpha) 120 | -------------------------------------------------------------------------------- /simulator/scripts/common.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | class RateConverter: 4 | """Implements helper functions to convert between human- 5 | readable rates (e.g., 10K) and numerical values (10000).""" 6 | 7 | @staticmethod 8 | def from_str(rate_str: Union[int, str]) -> int: 9 | """Helper function to parse a rate string (e.g., '1K').""" 10 | rate_map = {'K': 10 ** 3, 11 | 'M': 10 ** 6, 12 | 'G': 10 ** 9} 13 | 14 | assert rate_str != "" 15 | if isinstance(rate_str, str) and rate_str[-1] in rate_map.keys(): 16 | return int(float(rate_str[:-1]) * rate_map[rate_str[-1]]) 17 | 18 | else: return int(rate_str) 19 | 20 | @staticmethod 21 | def to_str(rate: Union[int, str], pretty=False) -> str: 22 | """Helper function to get a standardized string representation of 23 | a given rate or rate string. (e.g., 1000000 or '1000K' -> 1M).""" 24 | rate = RateConverter.from_str(rate) 25 | rate_str = str(rate) 26 | 27 | denominations = [(10 ** 9, 'G'), 28 | (10 ** 6, 'M'), 29 | (10 ** 3, 'K')] 30 | 31 | for (num, suffix) in denominations: 32 | if (rate >= num) and (rate % num == 0): 33 | rate_str = "{}{}{}".format(int(rate / num), 34 | " " if pretty else "", suffix) 35 | break 36 | 37 | return rate_str 38 | -------------------------------------------------------------------------------- /simulator/scripts/plot_results.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from collections import defaultdict 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | import itertools 6 | import os 7 | 8 | from common import RateConverter 9 | 10 | # PyPlot static parameters 11 | plt.rcParams['axes.grid'] = True 12 | plt.rcParams['grid.alpha'] = 0.25 13 | plt.rcParams['axes.axisbelow'] = True 14 | prop_cycle = plt.rcParams['axes.prop_cycle'] 15 | color_list = prop_cycle.by_key()['color'][:4] 16 | 17 | class DataPoint: 18 | """Represents a single output data-point.""" 19 | def __init__(self): 20 | self.avg_goodput = None # Average goodput 21 | self.ss_goodput = None # Steady-state goodput 22 | self.ss_alpha = None # Steady-state displacement factor 23 | 24 | def parse_data_point(output_fp: str) -> DataPoint: 25 | data_point = DataPoint() 26 | with open(output_fp, 'r') as log: 27 | for line in log: 28 | if "Average goodput" in line: 29 | values = [v.strip() for v in line.split(":")] 30 | assert len(values) == 2 # Sanity check 31 | data_point.avg_goodput = float( 32 | values[1].split(" ")[0].strip()) 33 | 34 | elif "Steady-state goodput" in line: 35 | values = [v.strip() for v in line.split(":")] 36 | assert len(values) == 2 # Sanity check 37 | data_point.ss_goodput = float( 38 | values[1].split(" ")[0].strip()) 39 | 40 | elif "Steady-state displacement factor" in line: 41 | values = [v.strip() for v in line.split(":")] 42 | assert len(values) == 2 # Sanity check 43 | data_point.ss_alpha = float(values[1]) 44 | 45 | return data_point 46 | 47 | def plot_data(results_dir: str) -> None: 48 | """Given a path to the results directory, plots the variation in 49 | goodput and DF as a function of the innocent and attack rate.""" 50 | outputs_dir = os.path.join(results_dir, "outputs") 51 | output_subdir_names = set(os.listdir(outputs_dir)) 52 | 53 | supported_policies = [ 54 | ("fcfs", "FCFS"), ("fq", "FQ"), 55 | ("sjf", "SJF"), ("sjf_inorder", "SJF-Inorder"), 56 | ("wsjf", "WSJF"), ("wsjf_inorder", "WSJF-Inorder")] 57 | 58 | # List of policies simulated 59 | policies = [] 60 | for policy_tuple in supported_policies: 61 | if policy_tuple[0] in output_subdir_names: 62 | policies.append(policy_tuple) 63 | 64 | # Datapoints 65 | recursive_dict = lambda: defaultdict(recursive_dict) 66 | datapoints = recursive_dict() 67 | 68 | # Simulated innocent and attack rates. We only pick those 69 | # rates for which all policies have simulated data-points. 70 | r_I_set_list, r_A_set_list = [], [] 71 | for policy_tag, _ in policies: 72 | output_subdir = os.path.join(outputs_dir, policy_tag) 73 | r_I_set, r_A_set = set(), set() 74 | 75 | for filename in os.listdir(output_subdir): 76 | output_fp = os.path.join(output_subdir, filename) 77 | values = os.path.splitext(filename)[0].split('_') 78 | 79 | # Sanity check: Expect I*_A*.out 80 | assert len(values) == 2 81 | assert values[0].startswith("I") 82 | assert values[1].startswith("A") 83 | 84 | # Extract the rates 85 | r_I_str = values[0][1:] 86 | r_A_str = values[1][1:] 87 | r_I = RateConverter.from_str(r_I_str) 88 | r_A = RateConverter.from_str(r_A_str) 89 | 90 | # Add the rates for this policy 91 | r_I_set.add(r_I) 92 | r_A_set.add(r_A) 93 | 94 | # Update the datapoints dict 95 | datapoints[policy_tag][r_I][r_A] = parse_data_point(output_fp) 96 | 97 | # Append the sets to the set-lists 98 | r_I_set_list.append(r_I_set) 99 | r_A_set_list.append(r_A_set) 100 | 101 | # Take the intersection of the sets 102 | r_I_list = sorted(set.intersection(*r_I_set_list)) 103 | r_A_list = sorted(set.intersection(*r_A_set_list)) 104 | 105 | if (len(policies) > 4) or (len(r_I_list) > 3): 106 | if len(policies) > 4: policies = policies[:4] 107 | if len(r_I_list) > 3: r_I_list = r_I_list[:3] 108 | print("Note: For readability, we only support plots with " 109 | "4 policies and 3 innocent input rates at a time.") 110 | 111 | # Generate the figure 112 | figure, axes = plt.subplots(nrows=2, ncols=len(r_I_list), sharex=True) 113 | 114 | # Iterators 115 | colors = itertools.cycle(color_list[:len(policies)]) 116 | markers = itertools.cycle(('v', 'x', '^', '+')[:len(policies)]) 117 | styles = itertools.cycle(('dashed', 'solid', 'solid', 'dashed')[:len(policies)]) 118 | 119 | # Normalize Y scale 120 | ymax_alpha = 0 121 | ymin_alpha = np.infty 122 | 123 | labels = [] 124 | handles = [] 125 | for col_idx, r_I in enumerate(r_I_list): 126 | for policy_tag, policy_name in policies: 127 | # Fetch the list of relevant datapoints 128 | policy_data = [datapoints[policy_tag][r_I][r_A] for r_A in r_A_list] 129 | 130 | # Fetch the corresponding DFs and goodputs 131 | alpha_list = [dp.ss_alpha for dp in policy_data] 132 | goodput_list = [dp.ss_goodput if policy_tag != "fcfs" 133 | else dp.avg_goodput for dp in policy_data] 134 | # Line params 135 | color = next(colors) 136 | style = next(styles) 137 | marker = next(markers) 138 | 139 | # Plot the goodput and the displacement factor 140 | axes[0, col_idx].plot( 141 | r_A_list, alpha_list, color=color, 142 | marker=marker, label=policy_name, 143 | markersize=8, linewidth=3, linestyle=style) 144 | 145 | handle = axes[1, col_idx].plot( 146 | r_A_list, goodput_list, color=color, 147 | marker=marker, label=policy_name, 148 | markersize=8, linewidth=3, linestyle=style) 149 | 150 | # Handle for legend 151 | if col_idx == 0: 152 | handles.append(handle) 153 | labels.append(policy_name) 154 | 155 | # Configure subgraphs 156 | axes[0, col_idx].set_xscale("log") 157 | axes[0, col_idx].set_yscale("log") 158 | axes[0, col_idx].tick_params(axis='y', labelsize=8, 159 | pad=0, rotation=45) 160 | axes[1, col_idx].set_xscale("log") 161 | axes[1, col_idx].set_ylim(( 162 | axes[1, col_idx].get_ylim()[0], (r_I / 10 ** 9) * 1.1)) 163 | 164 | axes[1, col_idx].tick_params(axis='y', labelsize=8) 165 | ylims = axes[0, col_idx].get_ylim() 166 | ymin_alpha = min(ymin_alpha, ylims[0]) 167 | ymax_alpha = max(ymax_alpha, ylims[1] * 10) 168 | 169 | # Re-label X axis ticks (in terms of bps) 170 | tick_values = [r_A_list[0]] 171 | for x in r_A_list: 172 | if x >= tick_values[-1] * 10: 173 | tick_values.append(x) 174 | 175 | if r_A_list[-1] > tick_values[-1]: 176 | tick_values.append(tick_values[-1] * 10) 177 | 178 | tick_labels = ["{}bps".format( 179 | RateConverter.to_str(x, pretty=True)) for x in tick_values] 180 | 181 | for col_idx in range(len(r_I_list)): 182 | axes[1, col_idx].set_xticks(tick_values) 183 | axes[1, col_idx].set_xticklabels(tick_labels, fontsize=8) 184 | axes[1, col_idx].tick_params(axis='x', rotation=15, pad=0) 185 | 186 | # Rescale Y axis 187 | for col_idx in range(len(r_I_list)): 188 | axes[0, col_idx].set_ylim((ymin_alpha, ymax_alpha)) 189 | 190 | # Add axes labels 191 | mid = int(len(r_I_list) / 2) 192 | axes[1, mid].set_xlabel("Attack Bandwidth (log-scale)") 193 | axes[1, 0].set_ylabel("Goodput (Gbps)", fontsize=10) 194 | axes[0, 0].set_ylabel("DF (log-scale)", fontsize=10) 195 | 196 | # Create the legend 197 | figure.legend(handles, labels=labels, 198 | loc="upper center", ncol=4, 199 | borderaxespad=0.1) 200 | plt.show() 201 | plt.close(figure) 202 | 203 | if __name__ == "__main__": 204 | parser = argparse.ArgumentParser("Plots simulation results.") 205 | parser.add_argument( 206 | "output_dir", type=str, 207 | help="Fully-qualified path to output directory containing results" 208 | ) 209 | 210 | # Parse arguments 211 | args = parser.parse_args() 212 | output_dir = args.output_dir 213 | 214 | # Plot results 215 | plot_data(output_dir) 216 | -------------------------------------------------------------------------------- /simulator/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Sources 2 | include_directories(.) 3 | add_executable(simulator 4 | simulator.cpp 5 | ) 6 | 7 | # Subdirs 8 | add_subdirectory(packet) 9 | add_subdirectory(applications) 10 | add_subdirectory(queueing) 11 | add_subdirectory(server) 12 | add_subdirectory(traffic) 13 | 14 | # Link libraries 15 | target_link_libraries(simulator common) 16 | target_link_libraries(simulator distributions) 17 | target_link_libraries(simulator simulator_packet) 18 | target_link_libraries(simulator simulator_applications) 19 | target_link_libraries(simulator simulator_queueing) 20 | target_link_libraries(simulator simulator_server) 21 | target_link_libraries(simulator simulator_traffic) 22 | -------------------------------------------------------------------------------- /simulator/src/applications/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(simulator_applications STATIC 4 | application_factory.cpp 5 | application.cpp 6 | echo.cpp 7 | iid_job_sizes.cpp 8 | tcp_reassembly.cpp 9 | ) 10 | 11 | target_link_libraries(simulator_applications common) 12 | -------------------------------------------------------------------------------- /simulator/src/applications/application.cpp: -------------------------------------------------------------------------------- 1 | #include "application.h" 2 | 3 | // STD headers 4 | #include 5 | #include 6 | 7 | /** 8 | * Application implementation. 9 | */ 10 | void Application::printConfiguration() const { 11 | std::cout << "{" 12 | << std::endl << "\ttype = " << kType << "," 13 | << std::endl << "\tstsf = " 14 | << std::fixed << std::setprecision(2) 15 | << kAppParams.getServiceTimeScaleFactor() << "," 16 | << std::endl << "\tuse_heuristic = " 17 | << (kAppParams.getUseHeuristic() ? "true" : "false") << "," 18 | << std::endl << "\tmax_attack_job_size_ns = " 19 | << kAppParams.getMaxAttackJobSizeNs() 20 | << std::endl << "}"; 21 | } 22 | -------------------------------------------------------------------------------- /simulator/src/applications/application.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_APPLICATIONS_APPLICATION_H 2 | #define SIMULATOR_APPLICATIONS_APPLICATION_H 3 | 4 | // Library headers 5 | #include "common/macros.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // STD headers 10 | #include "math.h" 11 | 12 | /** 13 | * Base class representing a generic network application. 14 | */ 15 | class Application { 16 | public: 17 | // Application parameters 18 | class Parameters final { 19 | private: 20 | bool use_heuristic_ = false; // Estimate job sizes? 21 | double service_time_scaling_ = 1; // Scaling factor 22 | double max_attack_job_size_ns_ = NAN; // Attacker's maximum job size 23 | 24 | public: 25 | DEFAULT_CTOR_AND_DTOR(Parameters); 26 | explicit Parameters(const bool uses_heuristic, 27 | const double scale_factor, 28 | const double max_attack_jsize) : 29 | use_heuristic_(uses_heuristic), 30 | service_time_scaling_(scale_factor), 31 | max_attack_job_size_ns_(max_attack_jsize) {} 32 | // Mutators 33 | void setUseHeuristic(const bool v) { use_heuristic_ = v; } 34 | void setServiceTimeScaling(const double v) { service_time_scaling_ = v; } 35 | void setMaxAttackJobSizeNs(const double v) { max_attack_job_size_ns_ = v; } 36 | 37 | // Accessors 38 | bool getUseHeuristic() const { return use_heuristic_; } 39 | double getMaxAttackJobSizeNs() const { return max_attack_job_size_ns_; } 40 | double getServiceTimeScaleFactor() const { return service_time_scaling_; } 41 | }; 42 | 43 | protected: 44 | const std::string kType; // App type 45 | const Parameters kAppParams; // App params 46 | explicit Application(const std::string type, 47 | const Parameters& params) : 48 | kType(type), kAppParams(params) {} 49 | /** 50 | * Internal helper function. Converts from context-dependent 51 | * application service time to context-agnostic job size (ns). 52 | */ 53 | double toJobSizeInNs(double service_time) const { 54 | if (service_time == kInvalidJobSize) { return kInvalidJobSize; } 55 | return service_time * kAppParams.getServiceTimeScaleFactor(); // Scale 56 | } 57 | 58 | public: 59 | virtual ~Application() {} 60 | DISALLOW_COPY_AND_ASSIGN(Application); 61 | 62 | /** 63 | * Returns the application type. 64 | */ 65 | const std::string& type() const { return kType; } 66 | 67 | /** 68 | * Prints the application parameters. 69 | */ 70 | void printConfiguration() const; 71 | 72 | /** 73 | * Returns whether this application requires flow ordering. 74 | */ 75 | virtual bool isFlowOrderRequired() const = 0; 76 | 77 | /** 78 | * Processes the given network packet by invoking the application- 79 | * specific implementation and returns the actual job size (in ns). 80 | */ 81 | virtual double process(const Packet& packet) = 0; 82 | 83 | /** 84 | * Returns the estimated time (in ns) to process the packet. 85 | */ 86 | virtual double getJobSizeEstimate(const Packet& packet) = 0; 87 | }; 88 | 89 | #endif // SIMULATOR_APPLICATIONS_APPLICATION_H 90 | -------------------------------------------------------------------------------- /simulator/src/applications/application_factory.cpp: -------------------------------------------------------------------------------- 1 | #include "application_factory.h" 2 | 3 | // Library headers 4 | #include "common/distributions/distribution_factory.h" 5 | #include "echo.h" 6 | #include "iid_job_sizes.h" 7 | #include "tcp_reassembly.h" 8 | 9 | // STD headers 10 | #include 11 | 12 | Application* ApplicationFactory:: 13 | generate(const libconfig::Setting& app_config) { 14 | Application* application = nullptr; 15 | Application::Parameters parameters; 16 | { 17 | // Parse common parameters 18 | bool use_heuristic = false; 19 | double service_time_scaling = 1; 20 | double max_attack_jsize_ns = NAN; 21 | 22 | // Use heuristics? 23 | app_config.lookupValue("heuristic", use_heuristic); 24 | 25 | // Service time scaling factor 26 | if (!app_config.lookupValue("stsf", service_time_scaling)) { 27 | throw std::runtime_error("Must specify 'stsf' (Service Time " 28 | "Scale Factor) for any application."); 29 | } 30 | // Maximum job size for attack traffic 31 | if (!app_config.lookupValue("max_attack_job_size_ns", max_attack_jsize_ns)) { 32 | throw std::runtime_error("Must specify 'max_attack_job_size_ns' " 33 | "(maximum job size (in ns) an attacker " 34 | "may use) for any application."); 35 | } 36 | // Update application parameters 37 | parameters.setUseHeuristic(use_heuristic); 38 | parameters.setMaxAttackJobSizeNs(max_attack_jsize_ns); 39 | parameters.setServiceTimeScaling(service_time_scaling); 40 | } 41 | std::string type; // Application type 42 | if (!app_config.lookupValue("type", type)) { 43 | throw std::runtime_error("No application type specified."); 44 | } 45 | // Echo application 46 | else if (type == Echo::name()) { 47 | application = new Echo(parameters); 48 | } 49 | // IID job sizes application 50 | else if (type == IIDJobSizes::name()) { 51 | // Parse the job size configuration 52 | if (!app_config.exists("job_size_ns_dist")) { 53 | throw std::runtime_error( 54 | "Must specify 'job_size_ns_dist' for IIDJobSizes application."); 55 | } 56 | else { 57 | libconfig::Setting& jsize_config = app_config["job_size_ns_dist"]; 58 | auto jsize_dist = DistributionFactory::generate(jsize_config); 59 | application = new IIDJobSizes(parameters, jsize_dist); 60 | } 61 | } 62 | // TCP Reassembly application 63 | else if (type == TCPReassembly::name()) { 64 | application = new TCPReassembly(parameters); 65 | } 66 | // Unknown application 67 | else { throw std::runtime_error( 68 | "Unknown application type: " + type + "."); } 69 | 70 | return application; 71 | } 72 | -------------------------------------------------------------------------------- /simulator/src/applications/application_factory.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_APPLICATIONS_APPLICATION_FACTORY_H 2 | #define SIMULATOR_APPLICATIONS_APPLICATION_FACTORY_H 3 | 4 | // Library headers 5 | #include "application.h" 6 | 7 | // Libconfig 8 | #include 9 | 10 | /** 11 | * Factory class for instantiating applications. 12 | */ 13 | class ApplicationFactory final { 14 | public: 15 | /** 16 | * Returns an application corresponding 17 | * to the parameterized configuration. 18 | */ 19 | static Application* 20 | generate(const libconfig::Setting& app_config); 21 | }; 22 | 23 | #endif // SIMULATOR_APPLICATIONS_APPLICATION_FACTORY_H 24 | -------------------------------------------------------------------------------- /simulator/src/applications/echo.cpp: -------------------------------------------------------------------------------- 1 | #include "echo.h" 2 | 3 | // STD headers 4 | #include 5 | 6 | double Echo::process(const Packet& packet) { 7 | // For attack traffic, use the packet-encoded job size 8 | if (packet.getClass() == TrafficClass::ATTACK) { 9 | assert(packet.getJobSizeActual() >= 0); 10 | return packet.getJobSizeActual(); 11 | } 12 | else { 13 | // By this point, the packet's job size field must 14 | // be set. As such, we simply return this value. 15 | return packet.getJobSizeEstimate(); 16 | } 17 | } 18 | 19 | double Echo::getJobSizeEstimate(const Packet& packet) { 20 | // Use the packet-encoded job size estimate 21 | assert(packet.getJobSizeEstimate() >= 0); 22 | return packet.getJobSizeEstimate(); 23 | } 24 | -------------------------------------------------------------------------------- /simulator/src/applications/echo.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_APPLICATIONS_ECHO_H 2 | #define SIMULATOR_APPLICATIONS_ECHO_H 3 | 4 | // Library headers 5 | #include "application.h" 6 | 7 | /** 8 | * A simple echo application. Uses the trace-specified job size. 9 | */ 10 | class Echo final : public Application { 11 | public: 12 | virtual ~Echo() {} 13 | explicit Echo(const Application::Parameters& p) : 14 | Application(name(), p) {} 15 | /** 16 | * Returns the application name. 17 | */ 18 | static std::string name() { return "echo"; }; 19 | 20 | /** 21 | * Returns whether this application requires flow ordering. 22 | */ 23 | virtual bool isFlowOrderRequired() const override { return false; } 24 | 25 | /** 26 | * Processes the given network packet by invoking the application- 27 | * specific implementation and returns the actual job size (in ns). 28 | */ 29 | virtual double process(const Packet& packet) override; 30 | 31 | /** 32 | * Returns the estimated time (in ns) to process the packet. 33 | */ 34 | virtual double getJobSizeEstimate(const Packet& packet) override; 35 | }; 36 | 37 | #endif // SIMULATOR_APPLICATIONS_ECHO_H 38 | -------------------------------------------------------------------------------- /simulator/src/applications/iid_job_sizes.cpp: -------------------------------------------------------------------------------- 1 | #include "iid_job_sizes.h" 2 | 3 | // Library headers 4 | #include "common/distributions/distribution_factory.h" 5 | 6 | double IIDJobSizes::process(const Packet& packet) { 7 | // For attack traffic, use the packet-encoded job size 8 | if (packet.getClass() == TrafficClass::ATTACK) { 9 | assert(packet.getJobSizeActual() == packet.getJobSizeEstimate()); 10 | assert(packet.getJobSizeActual() >= 0); 11 | return packet.getJobSizeActual(); 12 | } 13 | else { 14 | // By this point, the packet's job size field must 15 | // be set. As such, we simply return this value. 16 | assert(packet.getJobSizeEstimate() >= 0); 17 | return packet.getJobSizeEstimate(); 18 | } 19 | } 20 | 21 | double IIDJobSizes::getJobSizeEstimate(const Packet& packet) { 22 | // For attack traffic, use the packet-encoded job size 23 | if (packet.getClass() == TrafficClass::ATTACK) { 24 | assert(packet.getJobSizeEstimate() >= 0); 25 | return packet.getJobSizeEstimate(); 26 | } 27 | // Otherwise, sample from the distribution 28 | else { return jsize_dist_->sample(); } 29 | } 30 | -------------------------------------------------------------------------------- /simulator/src/applications/iid_job_sizes.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_APPLICATIONS_IID_JOB_SIZES_H 2 | #define SIMULATOR_APPLICATIONS_IID_JOB_SIZES_H 3 | 4 | // Library headers 5 | #include "application.h" 6 | #include "common/distributions/distribution.h" 7 | 8 | /** 9 | * Example application that picks job sizes for innocent 10 | * traffic i.i.d. from a user-specified distribution. 11 | */ 12 | class IIDJobSizes final : public Application { 13 | private: 14 | // Job size distribution 15 | Distribution* jsize_dist_ = nullptr; 16 | 17 | public: 18 | explicit IIDJobSizes(const Application::Parameters& params, 19 | Distribution* const jsize_dist) : 20 | Application(name(), params), 21 | jsize_dist_(jsize_dist) {} 22 | 23 | virtual ~IIDJobSizes() { delete(jsize_dist_); } 24 | 25 | /** 26 | * Returns the application name. 27 | */ 28 | static std::string name() { return "iid_job_sizes"; }; 29 | 30 | /** 31 | * Returns whether this application requires flow ordering. 32 | */ 33 | virtual bool isFlowOrderRequired() const override { return false; } 34 | 35 | /** 36 | * Processes the given network packet by invoking the application- 37 | * specific implementation and returns the actual job size (in ns). 38 | */ 39 | virtual double process(const Packet& packet) override; 40 | 41 | /** 42 | * Returns the estimated time (in ns) to process the packet. 43 | */ 44 | virtual double getJobSizeEstimate(const Packet& packet) override; 45 | }; 46 | 47 | #endif // SIMULATOR_APPLICATIONS_IID_JOB_SIZES_H 48 | -------------------------------------------------------------------------------- /simulator/src/applications/tcp_reassembly.cpp: -------------------------------------------------------------------------------- 1 | #include "tcp_reassembly.h" 2 | 3 | // STD headers 4 | #include 5 | 6 | /** 7 | * TCPFlowState implementation. 8 | */ 9 | typedef std::list> OOOList; 10 | typedef OOOList::const_iterator OOOListConstIter; 11 | 12 | uint32_t TCPFlowState::toServiceTime(const uint32_t num_traversals) { 13 | return kCostBase + (kCostPerTraversal * num_traversals); 14 | } 15 | 16 | std::pair TCPFlowState:: 17 | getIteratorAfterInsertionPosition(const Packet& packet) const { 18 | TCPHeader header = packet.getTCPHeader(); 19 | uint32_t num_traversals = 0; 20 | 21 | auto iter = ooo_list_.begin(); 22 | while (iter != ooo_list_.end()) { 23 | num_traversals++; 24 | 25 | // The end of the following PSN interval in the OOO list 26 | // is GEQ to the next expected PSN for the given packet. 27 | if (header.getNextSequenceNumber() <= iter->second) { break; } 28 | iter++; 29 | } 30 | return std::make_pair(iter, num_traversals); 31 | } 32 | 33 | double TCPFlowState::getServiceTimeEstimate(const Packet& packet) const { 34 | const TCPHeader& header = packet.getTCPHeader(); 35 | auto range = header.getSequenceNumberRange(); 36 | 37 | // SYN packet (or equivalent) 38 | if (header.getFlagSyn() || (next_psn_ == 0)) { 39 | return kInvalidJobSize; 40 | } 41 | // In-order flow, and this packet keeps it so 42 | else if (ooo_list_.empty() && (next_psn_ >= range.first)) { 43 | return kInvalidJobSize; 44 | } 45 | // If PSN is past the flow's TCP reassembly window, drop the packet 46 | else if (range.first > (next_psn_ + kReassemblyWindowSizeInBytes)) { 47 | return kInvalidJobSize; 48 | } 49 | // Obviously a duplicate packet 50 | else if (next_psn_ >= range.second) { return kInvalidJobSize; } 51 | 52 | // Out-of-order flow, approximate the expected 53 | // service time as the size of the OOO list. 54 | return static_cast( 55 | toServiceTime(ooo_list_.size())); 56 | } 57 | 58 | double TCPFlowState::process(const Packet& packet) { 59 | const TCPHeader& header = packet.getTCPHeader(); 60 | auto range = header.getSequenceNumberRange(); 61 | 62 | // SYN packet (or equivalent) 63 | if (header.getFlagSyn() || (next_psn_ == 0)) { 64 | if (ooo_list_.empty()) { // Actually a SYN packet 65 | next_psn_ = header.getNextSequenceNumber(); 66 | } 67 | // Sanity check: Duplicate SYN packet shouldn't increase the PSN 68 | else { assert(header.getNextSequenceNumber() <= next_psn_); } 69 | return kInvalidJobSize; 70 | } 71 | // The corresponding flow is in-order, and this packet keeps it so 72 | else if (ooo_list_.empty() && (next_psn_ >= range.first)) { 73 | next_psn_ = std::max(next_psn_, range.second); 74 | return kInvalidJobSize; 75 | } 76 | // If PSN is past the flow's TCP reassembly window, drop the packet 77 | else if (range.first > (next_psn_ + kReassemblyWindowSizeInBytes)) { 78 | return kInvalidJobSize; 79 | } 80 | // Obviously a duplicate packet 81 | else if (next_psn_ >= range.second) { return kInvalidJobSize; } 82 | 83 | auto pos = getIteratorAfterInsertionPosition(packet); 84 | uint32_t num_traversals = pos.second; // Traversals 85 | auto next_iter = pos.first; // Insertion location 86 | range.first = std::max(next_psn_, range.first); 87 | 88 | // Not inserting at the tail 89 | if (next_iter != ooo_list_.end()) { 90 | range.second = std::min( 91 | range.second, next_iter->first); 92 | } 93 | // Not inserting at the head 94 | if (next_iter != ooo_list_.begin()) { 95 | auto prev_iter = std::prev(next_iter); 96 | while (prev_iter != ooo_list_.begin() && 97 | prev_iter->first >= range.first) { 98 | 99 | // Erase the previous node 100 | prev_iter = std::prev( 101 | ooo_list_.erase(prev_iter)); 102 | } 103 | // Deleting the new head 104 | if (prev_iter->first >= range.first) { 105 | ooo_list_.erase(prev_iter); 106 | } 107 | // Update the range, if required 108 | else { 109 | range.first = std::max( 110 | range.first, prev_iter->second); 111 | } 112 | } 113 | // This packet has at least one new byte 114 | if (range.second > range.first) { 115 | // Insert this packet into the OOO list 116 | ooo_list_.insert(next_iter, range); 117 | 118 | // Finally, release any in-order nodes 119 | auto iter = ooo_list_.begin(); 120 | while (iter != ooo_list_.end() && 121 | next_psn_ == iter->first) { 122 | // Update the expected PSN 123 | next_psn_ = iter->second; 124 | iter = ooo_list_.erase(iter); 125 | 126 | // Update the traversal count 127 | num_traversals++; 128 | } 129 | } 130 | // Compute the service time 131 | return static_cast( 132 | toServiceTime(num_traversals)); 133 | } 134 | 135 | /** 136 | * TCPReassembly implementation. 137 | */ 138 | double TCPReassembly::process_( 139 | const Packet& packet, bool update) { 140 | double service_time = kInvalidJobSize; 141 | const TCPHeader& tcp_header = packet.getTCPHeader(); 142 | 143 | // Only require non-trivial processing for TCP packets 144 | if (tcp_header.isValid()) { 145 | // FIN/RST flags are set, terminate the flow 146 | if (tcp_header.isFlagFinOrRst()) { 147 | if (update) { 148 | auto iter = flows_.find(packet.getFlowId()); 149 | if (iter != flows_.end()) { flows_.erase(iter); } 150 | } 151 | } 152 | // Else, process innocent traffic as usual 153 | else if (!tcp_header.isPassThroughPacket()) { 154 | // Find or insert the corresponding flow mapping 155 | const FlowId& flow_id = packet.getFlowId(); 156 | auto iter = flows_.find(flow_id); 157 | if (update) { 158 | if (iter == flows_.end()) { 159 | iter = flows_.insert( 160 | {flow_id, TCPFlowState()}).first; 161 | } 162 | service_time = iter->second.process(packet); 163 | } 164 | // Flow exists, fetch its service time 165 | else if (iter != flows_.end()) { 166 | service_time = (iter->second. 167 | getServiceTimeEstimate(packet)); 168 | } 169 | } 170 | } 171 | return toJobSizeInNs(service_time); 172 | } 173 | 174 | double TCPReassembly::process(const Packet& packet) { 175 | // For attack traffic, use the packet-encoded job size 176 | if (packet.getClass() == TrafficClass::ATTACK) { 177 | assert(packet.getJobSizeActual() >= 0); 178 | return packet.getJobSizeActual(); 179 | } 180 | // If using the heuristic, process the packet 181 | else if (kAppParams.getUseHeuristic()) { 182 | return process_(packet, true); 183 | } 184 | // Else, processing is performed during job-size estimation; 185 | // there is nothing to do. Simply yields packet's job size. 186 | else { 187 | return packet.getJobSizeEstimate(); 188 | } 189 | } 190 | 191 | double TCPReassembly::getJobSizeEstimate(const Packet& packet) { 192 | // For attack traffic, use the packet-encoded job size 193 | if (packet.getClass() == TrafficClass::ATTACK) { 194 | assert(packet.getJobSizeEstimate() >= 0); 195 | return packet.getJobSizeEstimate(); 196 | } 197 | // Important note: In TCP reassembly, the only way to precisely 198 | // determine the size of a job is to serve all packets from the 199 | // same flow that appear before it. In order to compute the job 200 | // size, we preemptively process a job when getJobSizeEstimate() 201 | // is invoked. Since the underlying queue guarantees in-order 202 | // service for same-flow packets, the TCP state remains valid. 203 | assert(packet.getJobSizeEstimate() == kInvalidJobSize); 204 | return process_(packet, !kAppParams.getUseHeuristic()); 205 | } 206 | -------------------------------------------------------------------------------- /simulator/src/applications/tcp_reassembly.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_APPLICATIONS_TCP_REASSEMBLY_H 2 | #define SIMULATOR_APPLICATIONS_TCP_REASSEMBLY_H 3 | 4 | // Library headers 5 | #include "application.h" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | #include 11 | 12 | /** 13 | * Represents per-flow TCP state. 14 | */ 15 | class TCPFlowState final { 16 | private: 17 | typedef std::list> OOOList; 18 | typedef OOOList::const_iterator OOOListConstIter; 19 | 20 | // TODO(natre): Make these configurable parameters. 21 | static constexpr uint32_t kCostBase = 116; 22 | static constexpr uint32_t kCostPerTraversal = 4; 23 | static constexpr uint32_t kReassemblyWindowSizeInBytes = (1 << 16); 24 | 25 | // Housekeeping 26 | OOOList ooo_list_; // Out-of-order list 27 | uint32_t next_psn_; // Next sequence number 28 | 29 | /** 30 | * Internal helper method. Given the number of OOO linked-list 31 | * traversals to perform, returns the context-specific service 32 | * time for the corresponding packet. 33 | */ 34 | static uint32_t toServiceTime(const uint32_t num_traversals); 35 | 36 | /** 37 | * Returns: 1) An iterator to the insertion position 38 | * in the OOO list corresponding to this packet, and 39 | * 2) The number of required linked list traversals. 40 | */ 41 | std::pair 42 | getIteratorAfterInsertionPosition(const Packet& packet) const; 43 | 44 | public: 45 | explicit TCPFlowState() : next_psn_(0) {} 46 | 47 | /** 48 | * Returns the expected service time for this packet. 49 | */ 50 | double getServiceTimeEstimate(const Packet& packet) const; 51 | 52 | /** 53 | * Inserts the given packet into the appropriate position in the 54 | * OOO list, and returns the context-specific service time. Also 55 | * releases any segments that subsequently become in-order. 56 | */ 57 | double process(const Packet& packet); 58 | }; 59 | 60 | /** 61 | * Represents a TCP Reassembly engine. 62 | */ 63 | class TCPReassembly final : public Application { 64 | private: 65 | std::unordered_map flows_; // Flow ID -> Flow state 67 | 68 | /** 69 | * Internal helper method. Processes the given network 70 | * packet and returns the job size (in ns). 71 | */ 72 | double process_(const Packet& packet, bool update); 73 | 74 | public: 75 | virtual ~TCPReassembly() {} 76 | explicit TCPReassembly(const Application::Parameters& p) : 77 | Application(name(), p) {} 78 | /** 79 | * Returns the application name. 80 | */ 81 | static std::string name() { return "tcp_reassembly"; } 82 | 83 | /** 84 | * Returns whether this application requires flow ordering. 85 | */ 86 | virtual bool isFlowOrderRequired() const override { return true; } 87 | 88 | /** 89 | * Processes the given network packet by invoking the application- 90 | * specific implementation and returns the actual job size (in ns). 91 | */ 92 | virtual double process(const Packet& packet) override; 93 | 94 | /** 95 | * Returns the expected job size (in ns) to process the packet. 96 | */ 97 | virtual double getJobSizeEstimate(const Packet& packet) override; 98 | }; 99 | 100 | #endif // SIMULATOR_APPLICATIONS_TCP_REASSEMBLY_H 101 | -------------------------------------------------------------------------------- /simulator/src/packet/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(simulator_packet STATIC 4 | packet.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /simulator/src/packet/packet.cpp: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | 3 | // Library headers 4 | #include "common/macros.h" 5 | 6 | // STD headers 7 | #include 8 | 9 | // Boost headers 10 | #include 11 | 12 | /** 13 | * FlowId implementation. 14 | */ 15 | FlowId::FlowId() : FlowId(0, 0, 0, 0) {} 16 | FlowId::FlowId(const uint32_t s_ip, const uint32_t d_ip, 17 | const uint16_t s_port, const uint16_t d_port) : 18 | src_ip_(s_ip), dst_ip_(d_ip), src_port_(s_port), 19 | dst_port_(d_port) {} 20 | 21 | std::ostream& 22 | operator<<(std::ostream& out, const FlowId& id) { 23 | out << std::hex << std::setfill('0') 24 | << std::setw(8) << id.src_ip_ 25 | << std::setw(8) << id.dst_ip_ 26 | << std::setw(4) << id.src_port_ 27 | << std::setw(4) << id.dst_port_ 28 | << std::dec; 29 | return out; 30 | } 31 | 32 | FlowId FlowId::from(const uint32_t value) { 33 | return FlowId(value, 0, 0, 0); 34 | } 35 | 36 | size_t HashFlowId::operator()( 37 | const FlowId& flow_id) const { 38 | std::size_t res = 0; 39 | boost::hash_combine(res, flow_id.getSrcIP()); 40 | boost::hash_combine(res, flow_id.getDstIP()); 41 | boost::hash_combine(res, flow_id.getSrcPort()); 42 | boost::hash_combine(res, flow_id.getDstPort()); 43 | return res; 44 | } 45 | 46 | bool EqualToFlowId::operator()( 47 | const FlowId& a, const FlowId& b) const { 48 | return ((a.getSrcIP() == b.getSrcIP()) && 49 | (a.getDstIP() == b.getDstIP()) && 50 | (a.getSrcPort() == b.getSrcPort()) && 51 | (a.getDstPort() == b.getDstPort()) ); 52 | } 53 | 54 | /** 55 | * TCPHeader implementation. 56 | */ 57 | TCPHeader::TCPHeader() : TCPHeader( 58 | false, false, false, false, 0, 0) {} 59 | 60 | TCPHeader::TCPHeader( 61 | const bool is_valid, const bool syn, const bool fin, const 62 | bool rst, const uint32_t psn, const uint32_t next_psn) : 63 | is_valid_(is_valid), flag_syn_(syn), flag_fin_(fin), 64 | flag_rst_(rst), psn_(psn), next_psn_(next_psn) {} 65 | 66 | std::pair 67 | TCPHeader::getSequenceNumberRange() const { 68 | return std::make_pair(psn_, next_psn_); 69 | } 70 | 71 | /** 72 | * Packet implementation. 73 | */ 74 | Packet::Packet() : Packet( 75 | 0, FlowId(), TrafficClass::INNOCENT, 0) {} 76 | 77 | Packet::Packet(const uint64_t idx, const FlowId flow_id, 78 | const TrafficClass c, const uint32_t p) : 79 | idx_(idx), flow_id_(flow_id), class_(c), 80 | packet_size_(p), tcp_header_(), 81 | job_size_actual_(kInvalidJobSize), 82 | job_size_estimate_(kInvalidJobSize) {} 83 | -------------------------------------------------------------------------------- /simulator/src/packet/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_PACKET_PACKET_H 2 | #define SIMULATOR_PACKET_PACKET_H 3 | 4 | // STD headers 5 | #include 6 | #include 7 | 8 | // Constant parameters 9 | constexpr uint32_t kPacketSizeMinimumInBits = 512; // 64B 10 | constexpr uint32_t kPacketSizeMaximumInBits = 12144; // 1518B 11 | 12 | // Class of traffic (innocent or adversarial) 13 | enum class TrafficClass { INNOCENT = 0, ATTACK }; 14 | 15 | /** 16 | * Represents a Flow ID. 17 | */ 18 | class FlowId final { 19 | private: 20 | uint32_t src_ip_; // Source IP 21 | uint32_t dst_ip_; // Destination IP 22 | uint16_t src_port_; // Source port 23 | uint16_t dst_port_; // Destination port 24 | 25 | public: 26 | explicit FlowId(); 27 | explicit FlowId(const uint32_t s_ip, const uint32_t d_ip, 28 | const uint16_t s_port, const uint16_t d_port); 29 | 30 | // Accessor methods 31 | uint32_t getSrcIP() const { return src_ip_; } 32 | uint32_t getDstIP() const { return dst_ip_; } 33 | uint16_t getSrcPort() const { return src_port_; } 34 | uint16_t getDstPort() const { return dst_port_; } 35 | 36 | // Ostream operator 37 | friend std::ostream& 38 | operator<<(std::ostream& out, const FlowId& id); 39 | 40 | /** 41 | * Helper method to construct a flow 42 | * ID for non-networking workloads. 43 | */ 44 | static FlowId from(const uint32_t value); 45 | }; 46 | // Sanity check 47 | static_assert(sizeof(FlowId) == 12, "Bad FlowId packing"); 48 | 49 | /** 50 | * Recipe for hashing a flow ID. 51 | */ 52 | struct HashFlowId { 53 | size_t operator()(const FlowId& flow_id) const; 54 | }; 55 | 56 | /** 57 | * Equality checking for flow IDs. 58 | */ 59 | struct EqualToFlowId { 60 | bool operator()(const FlowId& a, const FlowId& b) const; 61 | }; 62 | 63 | /** 64 | * Represents TCP header data. 65 | */ 66 | class TCPHeader final { 67 | private: 68 | bool is_valid_; // Valid header? 69 | 70 | bool flag_syn_; // TCP SYN flag 71 | bool flag_fin_; // TCP FIN flag 72 | bool flag_rst_; // TCP RST flag 73 | uint32_t psn_; // Packet sequence number 74 | uint32_t next_psn_; // Next expected PSN 75 | 76 | public: 77 | explicit TCPHeader(); 78 | explicit TCPHeader( 79 | const bool is_valid, const bool syn, const bool fin, 80 | const bool rst, const uint32_t psn, const uint32_t next_psn); 81 | 82 | // Accessors 83 | bool isValid() const { return is_valid_; } 84 | bool getFlagSyn() const { return flag_syn_; } 85 | uint32_t getSequenceNumber() const { return psn_; } 86 | uint32_t getNextSequenceNumber() const { return next_psn_; } 87 | bool isPassThroughPacket() const { return (psn_ == next_psn_); } 88 | bool isFlagFinOrRst() const { return (flag_fin_ || flag_rst_); } 89 | 90 | // Returns a pair representing the packet's PSN range: [start, end) 91 | std::pair getSequenceNumberRange() const; 92 | }; 93 | 94 | /** 95 | * Represents a network packet. 96 | */ 97 | class Packet final { 98 | protected: 99 | uint64_t idx_; // (Unique) packet index 100 | FlowId flow_id_; // Corresponding flow ID 101 | TrafficClass class_; // Packet traffic class 102 | uint32_t packet_size_; // Packet size 103 | 104 | // TCP header data. TODO(natre): This should only 105 | // really exist in a version of the Packet class 106 | // that is specialized to TCP. 107 | TCPHeader tcp_header_; 108 | 109 | // Job sizes 110 | double job_size_actual_; 111 | double job_size_estimate_; 112 | 113 | // Housekeeping 114 | double arrive_time_ = 0; // Time of arrival 115 | double depart_time_ = 0; // Time of departure 116 | 117 | public: 118 | explicit Packet(); 119 | explicit Packet(const uint64_t idx, const FlowId flow_id, 120 | const TrafficClass c, const uint32_t p); 121 | // Accessors 122 | uint64_t getPacketIdx() const { return idx_; } 123 | TrafficClass getClass() const { return class_; } 124 | const FlowId& getFlowId() const { return flow_id_; } 125 | double getArriveTime() const { return arrive_time_; } 126 | double getDepartTime() const { return depart_time_; } 127 | uint32_t getPacketSize() const { return packet_size_; } 128 | double getJobSizeActual() const { return job_size_actual_; } 129 | const TCPHeader& getTCPHeader() const { return tcp_header_; } 130 | double getJobSizeEstimate() const { return job_size_estimate_; } 131 | 132 | std::string getClassTag() const { 133 | return (class_ == TrafficClass::ATTACK) ? "A" : "I"; 134 | } 135 | 136 | double getLatency() const { 137 | if (depart_time_ < arrive_time_) { 138 | throw std::runtime_error("Departure time must be GEQ arrival time"); 139 | } 140 | return (depart_time_ - arrive_time_); 141 | } 142 | // Mutators 143 | void setDepartTime(const double time) { depart_time_ = time; } 144 | void setArriveTime(const double time) { arrive_time_ = time; } 145 | void setTCPHeader(const TCPHeader& header) { tcp_header_ = header; } 146 | void setJobSizeActual(const double jsize) { job_size_actual_ = jsize; } 147 | void setJobSizeEstimate(const double jsize) { job_size_estimate_ = jsize; } 148 | }; 149 | 150 | #endif // SIMULATOR_PACKET_PACKET_H 151 | -------------------------------------------------------------------------------- /simulator/src/queueing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(simulator_queueing STATIC 4 | fcfs_queue.cpp 5 | fq_queue.cpp 6 | queue_factory.cpp 7 | sjf_queue.cpp 8 | sjf_inorder_queue.cpp 9 | wsjf_queue.cpp 10 | wsjf_inorder_queue.cpp 11 | ) 12 | 13 | target_link_libraries(simulator_queueing common) 14 | -------------------------------------------------------------------------------- /simulator/src/queueing/base_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_BASE_QUEUE_H 2 | #define SIMULATOR_QUEUEING_BASE_QUEUE_H 3 | 4 | // Library headers 5 | #include "packet/packet.h" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | 11 | /** 12 | * Base class representing a generic queue. 13 | */ 14 | class BaseQueue { 15 | protected: 16 | const std::string kType; // Underlying policy name 17 | explicit BaseQueue(const std::string type) : kType(type) {} 18 | 19 | // Internal helper method. Throws an error if 20 | // attempting to pop or peek an empty queue. 21 | static void assertNotEmpty(const bool empty) { 22 | if (empty) { throw std::runtime_error( 23 | "Cannot peek/pop an empty queue."); 24 | } 25 | } 26 | 27 | public: 28 | virtual ~BaseQueue() {} 29 | 30 | /** 31 | * Returns the policy name. 32 | */ 33 | const std::string& type() const { return kType; } 34 | 35 | /** 36 | * Returns the number of packets in the queue. 37 | */ 38 | virtual size_t size() const = 0; 39 | 40 | /** 41 | * Returns whether the packet queue is empty. 42 | */ 43 | virtual bool empty() const = 0; 44 | 45 | /** 46 | * Returns whether this queue maintains flow ordering. 47 | */ 48 | virtual bool isFlowOrderMaintained() const = 0; 49 | 50 | /** 51 | * Pops (and returns) the packet at the front of the queue. 52 | * @throw runtime error if the queue is currently empty. 53 | */ 54 | virtual Packet pop() = 0; 55 | 56 | /** 57 | * Returns (w/o popping) the packet at the front of the queue. 58 | * @throw runtime error if the queue is currently empty. 59 | */ 60 | virtual Packet peek() const = 0; 61 | 62 | /** 63 | * Pushes a new packet onto the queue. 64 | */ 65 | virtual void push(const Packet& packet) = 0; 66 | }; 67 | 68 | #endif // SIMULATOR_QUEUEING_BASE_QUEUE_H 69 | -------------------------------------------------------------------------------- /simulator/src/queueing/fcfs_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "fcfs_queue.h" 2 | 3 | Packet FCFSQueue::pop() { 4 | BaseQueue::assertNotEmpty(empty()); 5 | Packet packet = queue_.front(); 6 | queue_.pop_front(); 7 | return packet; 8 | } 9 | 10 | Packet FCFSQueue::peek() const { 11 | BaseQueue::assertNotEmpty(empty()); 12 | return queue_.front(); 13 | } 14 | 15 | void FCFSQueue::push(const Packet& packet) { 16 | queue_.push_back(packet); 17 | } 18 | -------------------------------------------------------------------------------- /simulator/src/queueing/fcfs_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_FCFS_QUEUE_H 2 | #define SIMULATOR_QUEUEING_FCFS_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "packet/packet.h" 7 | 8 | // STD headers 9 | #include 10 | 11 | /** 12 | * Represents an FCFS queue. 13 | */ 14 | class FCFSQueue : public BaseQueue { 15 | private: 16 | std::list queue_; // Packet queue 17 | 18 | public: 19 | explicit FCFSQueue() : BaseQueue(name()) {} 20 | virtual ~FCFSQueue() {} 21 | 22 | /** 23 | * Returns the policy name. 24 | */ 25 | static std::string name() { return "fcfs"; } 26 | 27 | /** 28 | * Returns the number of packets in the queue. 29 | */ 30 | virtual size_t size() const override { return queue_.size(); } 31 | 32 | /** 33 | * Returns whether the packet queue is empty. 34 | */ 35 | virtual bool empty() const override { return queue_.empty(); } 36 | 37 | /** 38 | * Returns whether this queue maintains flow ordering. 39 | */ 40 | virtual bool isFlowOrderMaintained() const override { return true; } 41 | 42 | /** 43 | * Pops (and returns) the packet at the front of the queue. 44 | * @throw runtime error if the queue is currently empty. 45 | */ 46 | virtual Packet pop() override; 47 | 48 | /** 49 | * Returns (w/o popping) the packet at the front of the queue. 50 | * @throw runtime error if the queue is currently empty. 51 | */ 52 | virtual Packet peek() const override; 53 | 54 | /** 55 | * Pushes a new packet onto the queue. 56 | */ 57 | virtual void push(const Packet& packet) override; 58 | }; 59 | 60 | #endif // SIMULATOR_QUEUEING_FCFS_QUEUE_H 61 | -------------------------------------------------------------------------------- /simulator/src/queueing/fq_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "fq_queue.h" 2 | 3 | // STD headers 4 | #include 5 | 6 | /** 7 | * FQFlowMetadata implementation. 8 | */ 9 | FQFlowMetadata& FQFlowMetadata::push(const Packet& packet) { 10 | // Update the flow's virtual clock. If the flow queue is not empty 11 | // when this packet arrives, use the existing virtual clock. Else, 12 | // compute the virtual clock relative to the arrival time. 13 | virtual_clock_ = ( 14 | packet.getJobSizeEstimate() + 15 | ((num_packets_ != 0) ? virtual_clock_ : 16 | packet.getArriveTime())); 17 | num_packets_++; 18 | return *this; 19 | } 20 | 21 | bool FQFlowMetadata::pop() { 22 | if (num_packets_ == 0) { 23 | throw std::runtime_error("Cannot pop an empty flow queue."); 24 | } 25 | num_packets_--; 26 | return (num_packets_ == 0); 27 | } 28 | 29 | /** 30 | * FQQueue implementation. 31 | */ 32 | Packet FQQueue::pop() { 33 | BaseQueue::assertNotEmpty(empty()); 34 | Packet packet = queue_.top().tag(); 35 | queue_.pop(); // Pop the global queue 36 | 37 | // Update the flow metadata 38 | auto iter = data_.find(packet.getFlowId()); 39 | assert(iter != data_.end()); // Sanity check 40 | if (iter->second.pop()) { data_.erase(iter); } 41 | 42 | size_--; // Decrement the global queue size 43 | return packet; 44 | } 45 | 46 | Packet FQQueue::peek() const { 47 | BaseQueue::assertNotEmpty(empty()); 48 | return queue_.top().tag(); 49 | } 50 | 51 | void FQQueue::push(const Packet& packet) { 52 | // Fetch (and update) the flow metadata 53 | const FQFlowMetadata data = data_[ 54 | packet.getFlowId()].push(packet); 55 | 56 | FQPriorityEntry entry(packet, data.getVirtualClock(), 57 | packet.getArriveTime()); 58 | 59 | queue_.push(entry); // Insert packet into the queue 60 | size_++; // Increment the global queue size 61 | } 62 | -------------------------------------------------------------------------------- /simulator/src/queueing/fq_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_FQ_QUEUE_H 2 | #define SIMULATOR_QUEUEING_FQ_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // STD headers 10 | #include 11 | 12 | // Boost headers 13 | #include 14 | 15 | /** 16 | * Per-flow metadata in a FQ-based queue. 17 | */ 18 | class FQFlowMetadata { 19 | private: 20 | size_t num_packets_ = 0; // Number of queued packets 21 | double virtual_clock_ = 0; // Virtual clock for the tail packet 22 | 23 | public: 24 | // Accessors 25 | size_t getNumPackets() const { return num_packets_; } 26 | double getVirtualClock() const { return virtual_clock_; } 27 | 28 | /** 29 | * Append a new packet to the flow queue. 30 | */ 31 | FQFlowMetadata& push(const Packet& packet); 32 | 33 | /** 34 | * Deque the packet at the front of the flow queue. 35 | * @return Whether the flow queue becomes empty. 36 | */ 37 | bool pop(); 38 | }; 39 | 40 | /** 41 | * Represents a FQ-based queue. 42 | */ 43 | class FQQueue : public BaseQueue { 44 | private: 45 | size_t size_ = 0; // Queue size 46 | typedef MinHeapEntry FQPriorityEntry; 47 | std::unordered_map data_; // Flow ID -> Metadata 49 | boost::heap::binomial_heap queue_; // Packet queue 50 | 51 | public: 52 | explicit FQQueue() : BaseQueue(name()) {} 53 | virtual ~FQQueue() {} 54 | 55 | /** 56 | * Returns the policy name. 57 | */ 58 | static std::string name() { return "fq"; } 59 | 60 | /** 61 | * Returns the number of packets in the queue. 62 | */ 63 | virtual size_t size() const override { return size_; } 64 | 65 | /** 66 | * Returns whether the packet queue is empty. 67 | */ 68 | virtual bool empty() const override { return (size_ == 0); } 69 | 70 | /** 71 | * Returns whether this queue maintains flow ordering. 72 | */ 73 | virtual bool isFlowOrderMaintained() const override { return true; } 74 | 75 | /** 76 | * Pops (and returns) the packet at the front of the queue. 77 | * @throw runtime error if the queue is currently empty. 78 | */ 79 | virtual Packet pop() override; 80 | 81 | /** 82 | * Returns (w/o popping) the packet at the front of the queue. 83 | * @throw runtime error if the queue is currently empty. 84 | */ 85 | virtual Packet peek() const override; 86 | 87 | /** 88 | * Pushes a new packet onto the queue. 89 | */ 90 | virtual void push(const Packet& packet) override; 91 | }; 92 | 93 | #endif // SIMULATOR_QUEUEING_FQ_QUEUE_H 94 | -------------------------------------------------------------------------------- /simulator/src/queueing/queue_factory.cpp: -------------------------------------------------------------------------------- 1 | #include "queue_factory.h" 2 | 3 | // Library headers 4 | #include "fcfs_queue.h" 5 | #include "fq_queue.h" 6 | #include "sjf_queue.h" 7 | #include "sjf_inorder_queue.h" 8 | #include "wsjf_queue.h" 9 | #include "wsjf_inorder_queue.h" 10 | 11 | // STD headers 12 | #include 13 | 14 | BaseQueue* QueueFactory:: 15 | generate(const libconfig::Setting& queue_config) { 16 | BaseQueue* queue = nullptr; 17 | std::string policy; // Queueing policy 18 | if (!queue_config.lookupValue("policy", policy)) { 19 | throw std::runtime_error("Must specify 'policy' to use."); 20 | } 21 | // FCFS queue 22 | else if (policy == FCFSQueue::name()) { 23 | queue = new FCFSQueue(); 24 | } 25 | // FQ queue 26 | else if (policy == FQQueue::name()) { 27 | queue = new FQQueue(); 28 | } 29 | // SJF queue 30 | else if (policy == SJFQueue::name()) { 31 | queue = new SJFQueue(); 32 | } 33 | // SJF-Inorder queue 34 | else if (policy == SJFInorderQueue::name()) { 35 | queue = new SJFInorderQueue(); 36 | } 37 | // WSJF queue 38 | else if (policy == WSJFQueue::name()) { 39 | queue = new WSJFQueue(); 40 | } 41 | // WSJF-Inorder queue 42 | else if (policy == WSJFInorderQueue::name()) { 43 | queue = new WSJFInorderQueue(); 44 | } 45 | // Unknown policy 46 | else { 47 | throw std::runtime_error( 48 | "Unknown queueing policy: " + policy + "."); 49 | } 50 | return queue; 51 | } 52 | -------------------------------------------------------------------------------- /simulator/src/queueing/queue_factory.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_QUEUE_FACTORY_H 2 | #define SIMULATOR_QUEUEING_QUEUE_FACTORY_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | 7 | // Libconfig 8 | #include 9 | 10 | /** 11 | * Factory class for instantiating queues. 12 | */ 13 | class QueueFactory final { 14 | public: 15 | /** 16 | * Returns a queue corresponding to the queueing 17 | * policy specified in the given configuration. 18 | */ 19 | static BaseQueue* 20 | generate(const libconfig::Setting& queue_config); 21 | }; 22 | 23 | #endif // SIMULATOR_QUEUEING_QUEUE_FACTORY_H 24 | -------------------------------------------------------------------------------- /simulator/src/queueing/sjf_inorder_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "sjf_inorder_queue.h" 2 | 3 | /** 4 | * SJFInorderFlowMetadata implementation. 5 | */ 6 | SJFInorderFlowMetadata& 7 | SJFInorderFlowMetadata::push(const Packet& packet) { 8 | // Update the flow's cumulative job size, and 9 | // insert the given packet into the queue. 10 | queue_.push(packet); 11 | total_jsize_ += packet.getJobSizeEstimate(); 12 | 13 | return *this; 14 | } 15 | 16 | Packet SJFInorderFlowMetadata::pop() { 17 | if (queue_.empty()) { 18 | throw std::runtime_error("Cannot pop an empty flow queue."); 19 | } 20 | // Update the flow queue state 21 | Packet packet = queue_.front(); 22 | total_jsize_ -= packet.getJobSizeEstimate(); 23 | assert(total_jsize_ >= 0); // Sanity check 24 | queue_.pop(); 25 | 26 | return packet; 27 | } 28 | 29 | /** 30 | * SJFInorderQueue implementation. 31 | */ 32 | Packet SJFInorderQueue::pop() { 33 | BaseQueue::assertNotEmpty(empty()); 34 | const FlowId flow_id = priorities_.top().tag(); 35 | auto iter = data_.find(flow_id); // Fetch metadata 36 | assert(iter != data_.end() && !iter->second.empty()); 37 | const Packet packet = iter->second.pop(); // Pop the flow queue 38 | 39 | // If the queue is not empty, update the correspoding 40 | // flow priority in the heap using the stored handle. 41 | if (!iter->second.empty()) { 42 | priorities_.update( 43 | iter->second.getHandle(), 44 | SJFPriorityEntry(flow_id, iter->second.getFlowRatio(), 45 | iter->second.front().getArriveTime()) 46 | ); 47 | } 48 | // Else, purge both the flow mapping and heap entry 49 | else { 50 | priorities_.pop(); 51 | data_.erase(iter); 52 | } 53 | 54 | size_--; // Update the global queue size 55 | return packet; 56 | } 57 | 58 | Packet SJFInorderQueue::peek() const { 59 | BaseQueue::assertNotEmpty(empty()); 60 | 61 | const FlowId flow_id = priorities_.top().tag(); 62 | return data_.at(flow_id).front(); 63 | } 64 | 65 | void SJFInorderQueue::push(const Packet& packet) { 66 | // Insert this packet into the flow queue. If this is the 67 | // HoL packet for this flow, also insert it into the heap. 68 | const FlowId flow_id = packet.getFlowId(); 69 | auto iter = data_.find(flow_id); 70 | if (iter == data_.end()) { 71 | 72 | SJFInorderFlowMetadata& data = data_[flow_id].push(packet); 73 | assert(data.size() == 1); // Sanity check 74 | handle_t handle = priorities_.push( 75 | SJFPriorityEntry(flow_id, data.getFlowRatio(), 76 | data.front().getArriveTime()) 77 | ); 78 | // Set the flow's heap handle 79 | data.setHandle(handle); 80 | } 81 | else { 82 | assert(!iter->second.empty()); // Sanity check 83 | SJFInorderFlowMetadata& data = iter->second.push(packet); 84 | priorities_.update( 85 | iter->second.getHandle(), 86 | SJFPriorityEntry(flow_id, data.getFlowRatio(), 87 | data.front().getArriveTime()) 88 | ); 89 | } 90 | size_++; // Update the global queue size 91 | } 92 | -------------------------------------------------------------------------------- /simulator/src/queueing/sjf_inorder_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_SJF_INORDER_QUEUE_H 2 | #define SIMULATOR_QUEUEING_SJF_INORDER_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // STD headers 10 | #include 11 | #include 12 | 13 | // Boost headers 14 | #include 15 | 16 | // Typedefs 17 | typedef MinHeapEntry SJFPriorityEntry; 18 | typedef boost::heap::binomial_heap::handle_type handle_t; 19 | 20 | /** 21 | * Per-flow metadata in an inorder SJF queue. 22 | */ 23 | class SJFInorderFlowMetadata { 24 | private: 25 | handle_t handle_; // Heap entry handle 26 | std::queue queue_; // Flow queue 27 | double total_jsize_ = 0; // Cumulative job size (numerator) 28 | 29 | public: 30 | // Accessors 31 | handle_t getHandle() const { return handle_; } 32 | double getTotalJobSize() const { return total_jsize_; } 33 | double getFlowRatio() const { return (total_jsize_ / queue_.size()); } 34 | 35 | // Mutators 36 | void setHandle(const handle_t handle) { handle_ = handle; } 37 | 38 | /** 39 | * Flow queue operations. 40 | */ 41 | size_t size() const { return queue_.size(); } 42 | bool empty() const { return queue_.empty(); } 43 | Packet front() const { return queue_.front(); } 44 | 45 | /** 46 | * Append a new packet to the flow queue. 47 | */ 48 | SJFInorderFlowMetadata& push(const Packet& packet); 49 | 50 | /** 51 | * Deque the packet at the front of the flow queue. 52 | */ 53 | Packet pop(); 54 | }; 55 | 56 | /** 57 | * Represents a flow-based, in-order SJF queue that schedules 58 | * packets in increasing order of (Sigma(J_{i}) / n), where 59 | * J_{i} and n represent the job size of each queued packet 60 | * and the size of the queue for that flow, respectively. 61 | */ 62 | class SJFInorderQueue : public BaseQueue { 63 | private: 64 | // Heap-based queue implementation 65 | size_t size_ = 0; 66 | boost::heap::binomial_heap priorities_; 67 | std::unordered_map data_; // Flow ID -> Metadata 69 | public: 70 | explicit SJFInorderQueue() : BaseQueue(name()) {} 71 | virtual ~SJFInorderQueue() {} 72 | 73 | /** 74 | * Returns the policy name. 75 | */ 76 | static std::string name() { return "sjf_inorder"; } 77 | 78 | /** 79 | * Returns the number of packets in the queue. 80 | */ 81 | virtual size_t size() const override { return size_; } 82 | 83 | /** 84 | * Returns whether the packet queue is empty. 85 | */ 86 | virtual bool empty() const override { return (size_ == 0); } 87 | 88 | /** 89 | * Returns whether this queue maintains flow ordering. 90 | */ 91 | virtual bool isFlowOrderMaintained() const override { return true; } 92 | 93 | /** 94 | * Pops (and returns) the packet at the front of the queue. 95 | * @throw runtime error if the queue is currently empty. 96 | */ 97 | virtual Packet pop() override; 98 | 99 | /** 100 | * Returns (w/o popping) the packet at the front of the queue. 101 | * @throw runtime error if the queue is currently empty. 102 | */ 103 | virtual Packet peek() const override; 104 | 105 | /** 106 | * Pushes a new packet onto the queue. 107 | */ 108 | virtual void push(const Packet& packet) override; 109 | }; 110 | 111 | #endif // SIMULATOR_QUEUEING_SJF_INORDER_QUEUE_H 112 | -------------------------------------------------------------------------------- /simulator/src/queueing/sjf_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "sjf_queue.h" 2 | 3 | Packet SJFQueue::pop() { 4 | BaseQueue::assertNotEmpty(empty()); 5 | Packet packet = queue_.top().tag(); 6 | queue_.pop(); 7 | return packet; 8 | } 9 | 10 | Packet SJFQueue::peek() const { 11 | BaseQueue::assertNotEmpty(empty()); 12 | return queue_.top().tag(); 13 | } 14 | 15 | void SJFQueue::push(const Packet& packet) { 16 | SJFPriorityEntry entry(packet, packet.getJobSizeEstimate(), 17 | packet.getArriveTime()); 18 | queue_.push(entry); 19 | } 20 | -------------------------------------------------------------------------------- /simulator/src/queueing/sjf_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_SJF_QUEUE_H 2 | #define SIMULATOR_QUEUEING_SJF_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // Boost headers 10 | #include 11 | 12 | /** 13 | * Represents an SJF queue. 14 | */ 15 | class SJFQueue : public BaseQueue { 16 | private: 17 | // Heap-based queue implementation 18 | typedef MinHeapEntry SJFPriorityEntry; 19 | boost::heap::binomial_heap queue_; 20 | 21 | public: 22 | explicit SJFQueue() : BaseQueue(name()) {} 23 | virtual ~SJFQueue() {} 24 | 25 | /** 26 | * Returns the policy name. 27 | */ 28 | static std::string name() { return "sjf"; } 29 | 30 | /** 31 | * Returns the number of packets in the queue. 32 | */ 33 | virtual size_t size() const override { return queue_.size(); } 34 | 35 | /** 36 | * Returns whether the packet queue is empty. 37 | */ 38 | virtual bool empty() const override { return queue_.empty(); } 39 | 40 | /** 41 | * Returns whether this queue maintains flow ordering. 42 | */ 43 | virtual bool isFlowOrderMaintained() const override { return false; } 44 | 45 | /** 46 | * Pops (and returns) the packet at the front of the queue. 47 | * @throw runtime error if the queue is currently empty. 48 | */ 49 | virtual Packet pop() override; 50 | 51 | /** 52 | * Returns (w/o popping) the packet at the front of the queue. 53 | * @throw runtime error if the queue is currently empty. 54 | */ 55 | virtual Packet peek() const override; 56 | 57 | /** 58 | * Pushes a new packet onto the queue. 59 | */ 60 | virtual void push(const Packet& packet) override; 61 | }; 62 | 63 | #endif // SIMULATOR_QUEUEING_SJF_QUEUE_H 64 | -------------------------------------------------------------------------------- /simulator/src/queueing/wsjf_inorder_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "wsjf_inorder_queue.h" 2 | 3 | /** 4 | * WSJFInorderFlowMetadata implementation. 5 | */ 6 | WSJFInorderFlowMetadata& 7 | WSJFInorderFlowMetadata::push(const Packet& packet) { 8 | // Update the flow's cumulative job and packet sizes, 9 | // and insert the given packet into the flow queue. 10 | queue_.push(packet); 11 | total_psize_ += packet.getPacketSize(); 12 | total_jsize_ += packet.getJobSizeEstimate(); 13 | 14 | return *this; 15 | } 16 | 17 | Packet WSJFInorderFlowMetadata::pop() { 18 | if (queue_.empty()) { 19 | throw std::runtime_error("Cannot pop an empty flow queue."); 20 | } 21 | // Update the flow queue state 22 | Packet packet = queue_.front(); 23 | total_psize_ -= packet.getPacketSize(); 24 | total_jsize_ -= packet.getJobSizeEstimate(); 25 | queue_.pop(); 26 | 27 | return packet; 28 | } 29 | 30 | /** 31 | * WSJFInorderQueue implementation. 32 | */ 33 | Packet WSJFInorderQueue::pop() { 34 | BaseQueue::assertNotEmpty(empty()); 35 | const FlowId flow_id = priorities_.top().tag(); 36 | auto iter = data_.find(flow_id); // Fetch metadata 37 | assert(iter != data_.end() && !iter->second.empty()); 38 | const Packet packet = iter->second.pop(); // Pop the flow queue 39 | 40 | // If the queue is not empty, update the correspoding 41 | // flow priority in the heap using the stored handle. 42 | if (!iter->second.empty()) { 43 | priorities_.update( 44 | iter->second.getHandle(), 45 | WSJFPriorityEntry(flow_id, iter->second.getFlowRatio(), 46 | iter->second.front().getArriveTime()) 47 | ); 48 | } 49 | // Else, purge both the flow mapping and heap entry 50 | else { 51 | priorities_.pop(); 52 | data_.erase(iter); 53 | } 54 | 55 | size_--; // Update the global queue size 56 | return packet; 57 | } 58 | 59 | Packet WSJFInorderQueue::peek() const { 60 | BaseQueue::assertNotEmpty(empty()); 61 | 62 | const FlowId flow_id = priorities_.top().tag(); 63 | return data_.at(flow_id).front(); 64 | } 65 | 66 | void WSJFInorderQueue::push(const Packet& packet) { 67 | // Insert this packet into the flow queue. If this is the 68 | // HoL packet for this flow, also insert it into the heap. 69 | const FlowId flow_id = packet.getFlowId(); 70 | auto iter = data_.find(flow_id); 71 | if (iter == data_.end()) { 72 | 73 | WSJFInorderFlowMetadata& data = data_[flow_id].push(packet); 74 | assert(data.size() == 1); // Sanity check 75 | handle_t handle = priorities_.push( 76 | WSJFPriorityEntry(flow_id, data.getFlowRatio(), 77 | data.front().getArriveTime()) 78 | ); 79 | // Set the flow's heap handle 80 | data.setHandle(handle); 81 | } 82 | else { 83 | assert(!iter->second.empty()); // Sanity check 84 | WSJFInorderFlowMetadata& data = iter->second.push(packet); 85 | priorities_.update( 86 | iter->second.getHandle(), 87 | WSJFPriorityEntry(flow_id, data.getFlowRatio(), 88 | data.front().getArriveTime()) 89 | ); 90 | } 91 | size_++; // Update the global queue size 92 | } 93 | -------------------------------------------------------------------------------- /simulator/src/queueing/wsjf_inorder_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_WSJF_INORDER_QUEUE_H 2 | #define SIMULATOR_QUEUEING_WSJF_INORDER_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // STD headers 10 | #include 11 | #include 12 | 13 | // Boost headers 14 | #include 15 | 16 | // Typedefs 17 | typedef MinHeapEntry WSJFPriorityEntry; 18 | typedef boost::heap::binomial_heap::handle_type handle_t; 19 | 20 | /** 21 | * Per-flow metadata in an inorder WSJF queue. 22 | */ 23 | class WSJFInorderFlowMetadata { 24 | private: 25 | handle_t handle_; // Heap entry handle 26 | std::queue queue_; // Flow queue 27 | double total_jsize_ = 0; // Cumulative job size (numerator) 28 | uint64_t total_psize_ = 0; // Cumulative packet size (denominator) 29 | 30 | public: 31 | // Accessors 32 | handle_t getHandle() const { return handle_; } 33 | double getTotalJobSize() const { return total_jsize_; } 34 | uint64_t getTotalPacketSize() const { return total_psize_; } 35 | double getFlowRatio() const { return (total_jsize_ / total_psize_); } 36 | 37 | // Mutators 38 | void setHandle(const handle_t handle) { handle_ = handle; } 39 | 40 | /** 41 | * Flow queue operations. 42 | */ 43 | size_t size() const { return queue_.size(); } 44 | bool empty() const { return queue_.empty(); } 45 | Packet front() const { return queue_.front(); } 46 | 47 | /** 48 | * Append a new packet to the flow queue. 49 | */ 50 | WSJFInorderFlowMetadata& push(const Packet& packet); 51 | 52 | /** 53 | * Deque the packet at the front of the flow queue. 54 | */ 55 | Packet pop(); 56 | }; 57 | 58 | /** 59 | * Represents a flow-based, in-order Weighted SJF queue that schedules 60 | * packets in increasing order of (Sigma(J_{i}) / Sigma(P_{i})), where 61 | * J_{q} and P_{q} represent the job and packet sizes of the queued 62 | * entries in each flow, respectively. 63 | */ 64 | class WSJFInorderQueue : public BaseQueue { 65 | private: 66 | // Heap-based queue implementation 67 | size_t size_ = 0; 68 | boost::heap::binomial_heap priorities_; 69 | std::unordered_map data_; // Flow ID -> Metadata 71 | 72 | public: 73 | explicit WSJFInorderQueue() : BaseQueue(name()) {} 74 | virtual ~WSJFInorderQueue() {} 75 | 76 | /** 77 | * Returns the policy name. 78 | */ 79 | static std::string name() { return "wsjf_inorder"; } 80 | 81 | /** 82 | * Returns the number of packets in the queue. 83 | */ 84 | virtual size_t size() const override { return size_; } 85 | 86 | /** 87 | * Returns whether the packet queue is empty. 88 | */ 89 | virtual bool empty() const override { return (size_ == 0); } 90 | 91 | /** 92 | * Returns whether this queue maintains flow ordering. 93 | */ 94 | virtual bool isFlowOrderMaintained() const override { return true; } 95 | 96 | /** 97 | * Pops (and returns) the packet at the front of the queue. 98 | * @throw runtime error if the queue is currently empty. 99 | */ 100 | virtual Packet pop() override; 101 | 102 | /** 103 | * Returns (w/o popping) the packet at the front of the queue. 104 | * @throw runtime error if the queue is currently empty. 105 | */ 106 | virtual Packet peek() const override; 107 | 108 | /** 109 | * Pushes a new packet onto the queue. 110 | */ 111 | virtual void push(const Packet& packet) override; 112 | }; 113 | 114 | #endif // SIMULATOR_QUEUEING_WSJF_INORDER_QUEUE_H 115 | -------------------------------------------------------------------------------- /simulator/src/queueing/wsjf_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "wsjf_queue.h" 2 | 3 | Packet WSJFQueue::pop() { 4 | BaseQueue::assertNotEmpty(empty()); 5 | Packet packet = queue_.top().tag(); 6 | queue_.pop(); 7 | return packet; 8 | } 9 | 10 | Packet WSJFQueue::peek() const { 11 | BaseQueue::assertNotEmpty(empty()); 12 | return queue_.top().tag(); 13 | } 14 | 15 | void WSJFQueue::push(const Packet& packet) { 16 | double metric = (packet.getJobSizeEstimate() / 17 | static_cast(packet.getPacketSize())); 18 | 19 | WSJFPriorityEntry entry(packet, metric, packet.getArriveTime()); 20 | queue_.push(entry); 21 | } 22 | -------------------------------------------------------------------------------- /simulator/src/queueing/wsjf_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_QUEUEING_WSJF_QUEUE_H 2 | #define SIMULATOR_QUEUEING_WSJF_QUEUE_H 3 | 4 | // Library headers 5 | #include "base_queue.h" 6 | #include "common/utils.h" 7 | #include "packet/packet.h" 8 | 9 | // Boost headers 10 | #include 11 | 12 | /** 13 | * Represents a Weighted SJF queue. 14 | */ 15 | class WSJFQueue : public BaseQueue { 16 | private: 17 | // Heap-based queue implementation 18 | typedef MinHeapEntry WSJFPriorityEntry; 19 | boost::heap::binomial_heap queue_; 20 | 21 | public: 22 | explicit WSJFQueue() : BaseQueue(name()) {} 23 | virtual ~WSJFQueue() {} 24 | 25 | /** 26 | * Returns the policy name. 27 | */ 28 | static std::string name() { return "wsjf"; } 29 | 30 | /** 31 | * Returns the number of packets in the queue. 32 | */ 33 | virtual size_t size() const override { return queue_.size(); } 34 | 35 | /** 36 | * Returns whether the packet queue is empty. 37 | */ 38 | virtual bool empty() const override { return queue_.empty(); } 39 | 40 | /** 41 | * Returns whether this queue maintains flow ordering. 42 | */ 43 | virtual bool isFlowOrderMaintained() const override { return false; } 44 | 45 | /** 46 | * Pops (and returns) the packet at the front of the queue. 47 | * @throw runtime error if the queue is currently empty. 48 | */ 49 | virtual Packet pop() override; 50 | 51 | /** 52 | * Returns (w/o popping) the packet at the front of the queue. 53 | * @throw runtime error if the queue is currently empty. 54 | */ 55 | virtual Packet peek() const override; 56 | 57 | /** 58 | * Pushes a new packet onto the queue. 59 | */ 60 | virtual void push(const Packet& packet) override; 61 | }; 62 | 63 | #endif // SIMULATOR_QUEUEING_WSJF_QUEUE_H 64 | -------------------------------------------------------------------------------- /simulator/src/server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(simulator_server STATIC server.cpp) 4 | 5 | target_link_libraries(simulator_server common) 6 | target_link_libraries(simulator_server distributions) 7 | target_link_libraries(simulator_server simulator_packet) 8 | target_link_libraries(simulator_server simulator_applications) 9 | target_link_libraries(simulator_server simulator_queueing) 10 | -------------------------------------------------------------------------------- /simulator/src/server/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | // Library headers 4 | #include "common/utils.h" 5 | #include "packet/packet.h" 6 | 7 | // STD headers 8 | #include 9 | 10 | Server::Server(Application* a, const BaseQueue* q) : app_(a) { 11 | // Ensure that the application's requirements 12 | // of per-flow packet ordering are respected. 13 | if (app_->isFlowOrderRequired() && 14 | !q->isFlowOrderMaintained()) { 15 | throw std::runtime_error( 16 | "Policy " + q->type() + " does not guarantee per-flow " + 17 | "ordering (required by application " + app_->type() + ")"); 18 | } 19 | } 20 | 21 | Server::~Server() { delete(app_); } 22 | 23 | /** 24 | * Sets the actual & estimated job sizes for the given packet. 25 | */ 26 | void Server::setJobSizeEstimateAndActual(Packet& packet) { 27 | packet.setJobSizeEstimate(app_->getJobSizeEstimate(packet)); 28 | packet.setJobSizeActual(app_->process(packet)); 29 | } 30 | 31 | /** 32 | * Record packet departure. 33 | */ 34 | Packet Server::recordDeparture() { 35 | packet_.setDepartTime(depart_time_); 36 | is_busy_ = false; 37 | return packet_; 38 | } 39 | 40 | /** 41 | * Schedule a new packet. 42 | */ 43 | void Server::schedule(const double time, Packet packet) { 44 | // Sanity checks 45 | assert(packet.getJobSizeEstimate() >= 0); 46 | assert(!is_busy_ && time >= depart_time_); 47 | const double jsize = packet.getJobSizeActual(); 48 | assert(jsize != kInvalidJobSize); // Sanity check 49 | 50 | // Update the current server state 51 | depart_time_ = time + jsize; 52 | packet_ = packet; 53 | is_busy_ = true; 54 | } 55 | -------------------------------------------------------------------------------- /simulator/src/server/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_SERVER_SERVER_H 2 | #define SIMULATOR_SERVER_SERVER_H 3 | 4 | // Library headers 5 | #include "packet/packet.h" 6 | #include "queueing/base_queue.h" 7 | #include "applications/application.h" 8 | 9 | /** 10 | * Represents a single, non-preemptive server. 11 | */ 12 | class Server { 13 | private: 14 | // Underlying application 15 | Application* app_ = nullptr; 16 | 17 | // Housekeeping 18 | bool is_busy_ = false; // Server busy? 19 | Packet packet_; // Packet currently being served 20 | double depart_time_ = 0; // Departure time for packet 21 | 22 | public: 23 | explicit Server(Application* a, const BaseQueue* q); 24 | ~Server(); 25 | 26 | // Accessor methods 27 | bool isBusy() const { return is_busy_; } 28 | Application* getApplication() const { return app_; } 29 | double getDepartureTime() const { return depart_time_; } 30 | 31 | /** 32 | * Sets the estimated & actual job sizes for the parameterized packet. 33 | * Note: This method MUST be invoked on each packet before scheduling 34 | * it or inserting it into the packet queue. As a corollary, packets' 35 | * job size fields must not be used prior to this invocation. 36 | */ 37 | void setJobSizeEstimateAndActual(Packet& packet); 38 | 39 | /** 40 | * Record packet departure. 41 | */ 42 | Packet recordDeparture(); 43 | 44 | /** 45 | * Schedule a new packet. 46 | */ 47 | void schedule(const double time, Packet packet); 48 | }; 49 | 50 | #endif // SIMULATOR_SERVER_SERVER_H 51 | -------------------------------------------------------------------------------- /simulator/src/simulator.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_SIMULATOR_H 2 | #define SIMULATOR_SIMULATOR_H 3 | 4 | // Library headers 5 | #include "queueing/base_queue.h" 6 | #include "server/server.h" 7 | #include "traffic/trafficgen.h" 8 | 9 | // Libconfig 10 | #include 11 | 12 | /** 13 | * Implements the core simulator functionality. 14 | */ 15 | class Simulator final { 16 | private: 17 | // Simulation config 18 | const bool kIsDryRun; // Dry run? 19 | uint64_t kMaxNumArrivals; // Max arrival count 20 | Server* server_ = nullptr; // Server implementation 21 | BaseQueue* queue_ = nullptr; // Queue implementation 22 | TrafficGenerator* tg_innocent_ = nullptr; // Innocent traffic-gen 23 | TrafficGenerator* tg_attack_ = nullptr; // Adversarial traffic-gen 24 | 25 | // Housekeeping 26 | bool done = false; 27 | 28 | // Helper method to parse configs 29 | void parseSimulationConfig(const libconfig::Setting& config); 30 | 31 | public: 32 | explicit Simulator(const bool is_dry_run, 33 | const libconfig::Setting& config); 34 | ~Simulator(); 35 | 36 | /** 37 | * Print the simulation configuration. 38 | */ 39 | void printConfig() const; 40 | 41 | /** 42 | * Run simulation. 43 | */ 44 | void run(const bool verbose, 45 | const std::string packets_fp=""); 46 | }; 47 | 48 | #endif // SIMULATOR_SIMULATOR_H 49 | -------------------------------------------------------------------------------- /simulator/src/traffic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build static library 2 | include_directories(.) 3 | add_library(simulator_traffic STATIC 4 | synthetic_trafficgen.cpp 5 | trafficgen_factory.cpp 6 | trace_trafficgen.cpp 7 | trafficgen.cpp 8 | ) 9 | 10 | target_link_libraries(simulator_traffic common) 11 | target_link_libraries(simulator_traffic distributions) 12 | -------------------------------------------------------------------------------- /simulator/src/traffic/synthetic_trafficgen.cpp: -------------------------------------------------------------------------------- 1 | #include "synthetic_trafficgen.h" 2 | 3 | // Library headers 4 | #include "common/utils.h" 5 | 6 | // STD headers 7 | #include 8 | #include 9 | 10 | /** 11 | * SyntheticTrafficGenerator implementation. 12 | */ 13 | void SyntheticTrafficGenerator::reset() { 14 | arrival_time_ = 0; 15 | next_flow_id_ = 0; 16 | } 17 | 18 | void SyntheticTrafficGenerator::updateArrivalTime() { 19 | arrival_time_ += iat_dist_->sample(); 20 | } 21 | 22 | Packet SyntheticTrafficGenerator::getNextArrival(const uint64_t packet_idx) { 23 | Packet packet = getNextArrivalImpl(packet_idx); 24 | packet.setArriveTime(arrival_time_); 25 | return packet; 26 | } 27 | 28 | /** 29 | * InnocentTrafficGenerator implementation. 30 | */ 31 | Packet InnocentTrafficGenerator:: 32 | getNextArrivalImpl(const uint64_t packet_idx) { 33 | // Generate the next (innocent) flow ID to use 34 | FlowId flow_id = FlowId::from(next_flow_id_); 35 | if (++next_flow_id_ == kNumFlows) { 36 | next_flow_id_ = 0; 37 | } 38 | // Generate a packet with this flow_id and packet size 39 | return Packet(packet_idx, flow_id, TrafficClass::INNOCENT, 40 | static_cast(psize_dist_->sample())); 41 | } 42 | 43 | double InnocentTrafficGenerator::getRateInBitsPerSecondImpl() const { 44 | // The rate is computed as E[P] / E[T], where P, T represent 45 | // packet and inter-arrival time distributions, respectively. 46 | return ((psize_dist_->getSampleStats().getMean() * 47 | kNanosecsPerSec) / iat_dist_->getSampleStats().getMean()); 48 | } 49 | 50 | double InnocentTrafficGenerator::getAveragePacketSizeInBitsImpl() const { 51 | return psize_dist_->getSampleStats().getMean(); 52 | } 53 | 54 | void InnocentTrafficGenerator::printConfiguration() const { 55 | std::cout << "{" 56 | << std::endl << "\ttype: " << name() << "," 57 | << std::endl << "\tiat_ns_dist: "; 58 | 59 | iat_dist_->printConfiguration(); 60 | std::cout << "," << std::endl << "\tpacket_size_bits_dist: "; 61 | 62 | psize_dist_->printConfiguration(); 63 | if (is_calibrated_) { 64 | std::cout << "," 65 | << std::endl << "\trate: " 66 | << getCalibratedRateInBitsPerSecond() << " bps"; 67 | } 68 | std::cout << std::endl << "}" << std::endl; 69 | } 70 | 71 | void InnocentTrafficGenerator::calibrate(const double rate) { 72 | if (isCalibrated()) { 73 | throw std::runtime_error("Traffic-generator was already calibrated."); 74 | } 75 | else if (!std::isnan(rate)) { 76 | if (!DoubleApproxEqual(rate, getRateInBitsPerSecondImpl())) { 77 | throw std::runtime_error("Calibration failed, check computed rate."); 78 | } 79 | else { is_calibrated_ = true; } 80 | } 81 | } 82 | 83 | /** 84 | * AttackTrafficGenerator implementation. 85 | */ 86 | AttackTrafficGenerator::AttackTrafficGenerator( 87 | const uint32_t num_flows, const uint32_t fid_offset, 88 | ConstantDistribution* const iat_dist, const uint32_t p, 89 | const double j) : SyntheticTrafficGenerator(num_flows, iat_dist), 90 | kFlowIdOffset(fid_offset), kAttackJobSizeNs(j), kAttackPacketSizeBits(p) { 91 | 92 | // Special case: zero attack bandwidth (innocent arrivals only) 93 | if (iat_dist_->getSampleStats().getMean() == kDblPosInfty) { 94 | arrival_time_ = kDblPosInfty; 95 | } 96 | } 97 | 98 | Packet AttackTrafficGenerator:: 99 | getNextArrivalImpl(const uint64_t packet_idx) { 100 | // Compute the current attack flow ID and generate the next one 101 | uint32_t flow_id = (kFlowIdOffset + next_flow_id_); 102 | if (++next_flow_id_ == kNumFlows) { 103 | next_flow_id_ = 0; 104 | } 105 | Packet p = Packet(packet_idx, FlowId::from(flow_id), 106 | TrafficClass::ATTACK, kAttackPacketSizeBits); 107 | 108 | // TODO(natre): Allow spoofing of job-size estimates 109 | p.setJobSizeEstimate(kAttackJobSizeNs); 110 | p.setJobSizeActual(kAttackJobSizeNs); 111 | return p; 112 | } 113 | 114 | double AttackTrafficGenerator::getRateInBitsPerSecondImpl() const { 115 | // The rate is computed as E[P] / E[T], where P, T represent 116 | // packet and inter-arrival time distributions, respectively. 117 | return ((kAttackPacketSizeBits * kNanosecsPerSec) / 118 | iat_dist_->getSampleStats().getMean()); 119 | } 120 | 121 | double AttackTrafficGenerator::getAveragePacketSizeInBitsImpl() const { 122 | return kAttackPacketSizeBits; 123 | } 124 | 125 | void AttackTrafficGenerator::printConfiguration() const { 126 | std::cout << "{" 127 | << std::endl << "\ttype: " << name() << "," 128 | << std::endl << "\tiat_ns_dist: "; 129 | 130 | iat_dist_->printConfiguration(); 131 | if (is_calibrated_) { 132 | std::cout << "," 133 | << std::endl << "\tpacket_size_bits: " 134 | << std::fixed << std::setprecision(2) 135 | << kAttackPacketSizeBits << " bits," 136 | << std::endl << "\tjob_size_ns: " 137 | << kAttackJobSizeNs << " ns," 138 | << std::endl << "\trate: " 139 | << getCalibratedRateInBitsPerSecond() << " bps"; 140 | } 141 | std::cout << std::endl << "}" 142 | << std::endl; 143 | } 144 | 145 | void AttackTrafficGenerator::calibrate(const double rate) { 146 | if (isCalibrated()) { 147 | throw std::runtime_error("Traffic-generator was already calibrated."); 148 | } 149 | else if (!std::isnan(rate)) { 150 | if (!DoubleApproxEqual(rate, getRateInBitsPerSecondImpl())) { 151 | throw std::runtime_error("Calibration failed, check computed rate."); 152 | } 153 | else { is_calibrated_ = true; } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /simulator/src/traffic/synthetic_trafficgen.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_TRAFFIC_SYNTHETIC_TRAFFICGEN_H 2 | #define SIMULATOR_TRAFFIC_SYNTHETIC_TRAFFICGEN_H 3 | 4 | // Library headers 5 | #include "common/utils.h" 6 | #include "common/distributions/constant_distribution.h" 7 | #include "common/distributions/distribution.h" 8 | #include "packet/packet.h" 9 | #include "trafficgen.h" 10 | 11 | /** 12 | * Base Class representing a synthetic traffic generator. 13 | */ 14 | class SyntheticTrafficGenerator : public TrafficGenerator { 15 | protected: 16 | // TODO(natre): Currently, this is hardcoded to use constant 17 | // inter-arrival times (representing a fluid model). Ideally, 18 | // the distribution should be a user-configurable parameter. 19 | ConstantDistribution* iat_dist_ = nullptr; // IATs (ns) 20 | const uint32_t kNumFlows = 1; // Number of flows to use 21 | 22 | // Housekeeping 23 | uint32_t next_flow_id_ = 0; 24 | bool is_calibrated_ = false; // Calibrated? 25 | 26 | explicit SyntheticTrafficGenerator( 27 | const uint32_t num_flows, ConstantDistribution* const iat_dist) : 28 | TrafficGenerator(name()), iat_dist_(iat_dist), kNumFlows(num_flows) {} 29 | 30 | /** 31 | * Helper method. Returns the next packet arrival. 32 | */ 33 | virtual Packet getNextArrivalImpl(const uint64_t packet_idx) = 0; 34 | 35 | public: 36 | virtual ~SyntheticTrafficGenerator() { delete(iat_dist_); } 37 | 38 | /** 39 | * Returns the traffic-gen name. 40 | */ 41 | static std::string name() { return "synthetic"; } 42 | 43 | // Accessors 44 | virtual uint32_t getNumFlows() const override { return kNumFlows; } 45 | 46 | /** 47 | * Resets the traffic-generator to its initial state. 48 | */ 49 | virtual void reset() override; 50 | 51 | /** 52 | * Invokes the implementation-specific virtual method to update the 53 | * arrival time. TODO(natre): This is a hack. Ideally, the traffic- 54 | * generator should update the next arrival time of its own accord; 55 | * however, in some instances, the arrival rate is predicated on a 56 | * packet's true job size, which may not be known at this point. 57 | */ 58 | virtual void updateArrivalTime() override; 59 | 60 | /** 61 | * Returns the next packet arrival by invoking the implementation- 62 | * specific virtual method. 63 | */ 64 | virtual Packet getNextArrival(const uint64_t packet_idx) override; 65 | 66 | /** 67 | * Returns whether the traffic-generator is calibrated. 68 | * 69 | * For some traffic-gens (eg, trace-driven), the total packet bit- 70 | * rate ultimately depends on the workload's average packet size. 71 | * Since this is only determined after running a full simulation, 72 | * the first simulation run must be a "dry-run" of the trace. We 73 | * say that the traffic-generator is "calibrated" if the average 74 | * packet size is already known (ie, this is not a dry-run). 75 | */ 76 | virtual bool isCalibrated() const override { return is_calibrated_; } 77 | }; 78 | 79 | /** 80 | * Represents a traffic generator that generates packets with sizes 81 | * chosen from a user-specified distribution. This corresponds to 82 | * the innocent traffic workload. 83 | */ 84 | class InnocentTrafficGenerator final : public SyntheticTrafficGenerator { 85 | private: 86 | // Packet size distribution 87 | Distribution* psize_dist_ = nullptr; 88 | 89 | /** 90 | * Returns the next arrival and updates the arrival clock. 91 | */ 92 | virtual Packet getNextArrivalImpl(const uint64_t packet_idx) override; 93 | 94 | // Accessors 95 | virtual double getRateInBitsPerSecondImpl() const override; 96 | virtual double getAveragePacketSizeInBitsImpl() const override; 97 | 98 | public: 99 | virtual ~InnocentTrafficGenerator() { delete(psize_dist_); } 100 | explicit InnocentTrafficGenerator(const uint32_t num_flows, 101 | ConstantDistribution* const iat_dist, Distribution* const psize_dist) : 102 | SyntheticTrafficGenerator(num_flows, iat_dist), psize_dist_(psize_dist) {} 103 | 104 | /** 105 | * Print the distribution configuration. 106 | */ 107 | virtual void printConfiguration() const override; 108 | 109 | // Calibrate the traffic-generator 110 | void calibrate(const double rate); 111 | }; 112 | 113 | /** 114 | * Represents an adversarial traffic generator. 115 | */ 116 | class AttackTrafficGenerator final : public SyntheticTrafficGenerator { 117 | private: 118 | const uint32_t kFlowIdOffset; // Flow ID offset 119 | const double kAttackJobSizeNs; // Adversarial job size (ns) 120 | const uint32_t kAttackPacketSizeBits; // Adversarial packet size (bits) 121 | 122 | /** 123 | * Returns the next arrival and updates the arrival clock. 124 | */ 125 | virtual Packet getNextArrivalImpl(const uint64_t packet_idx) override; 126 | 127 | // Accessors 128 | virtual double getRateInBitsPerSecondImpl() const override; 129 | virtual double getAveragePacketSizeInBitsImpl() const override; 130 | 131 | public: 132 | explicit AttackTrafficGenerator(); 133 | explicit AttackTrafficGenerator(const uint32_t num_flows, 134 | const uint32_t fid_offset, ConstantDistribution* const 135 | iat_dist, const uint32_t psize_bits, const double jsize_ns); 136 | 137 | virtual ~AttackTrafficGenerator() {} 138 | 139 | /** 140 | * Print the distribution configuration. 141 | */ 142 | virtual void printConfiguration() const override; 143 | 144 | // Calibrate the traffic-generator 145 | void calibrate(const double rate); 146 | }; 147 | 148 | #endif // SIMULATOR_TRAFFIC_SYNTHETIC_TRAFFICGEN_H 149 | -------------------------------------------------------------------------------- /simulator/src/traffic/trace_trafficgen.cpp: -------------------------------------------------------------------------------- 1 | #include "trace_trafficgen.h" 2 | 3 | // Library headers 4 | #include "common/macros.h" 5 | 6 | // STD headers 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | TraceTrafficGenerator::TraceTrafficGenerator(const std::string& trace_fp, 13 | ConstantDistribution* const iat_dist) : TrafficGenerator(name()), 14 | trace_fp_(trace_fp), iat_dist_(iat_dist) { 15 | // Initialize the trace filestream 16 | trace_ifs_.open(trace_fp); 17 | updateHasNewArrival(); 18 | } 19 | 20 | TraceTrafficGenerator::~TraceTrafficGenerator() { 21 | // Deallocate resources 22 | trace_ifs_.close(); 23 | delete(iat_dist_); 24 | } 25 | 26 | void TraceTrafficGenerator::updateHasNewArrival() { 27 | has_new_arrival_ = (trace_ifs_.peek() != 28 | std::ifstream::traits_type::eof()); 29 | } 30 | 31 | double TraceTrafficGenerator::getRateInBitsPerSecondImpl() const { 32 | // The rate is computed as E[P] / E[T], where P, T represent 33 | // packet and inter-arrival time distributions, respectively. 34 | return ((avg_psize_ * kNanosecsPerSec) / 35 | iat_dist_->getSampleStats().getMean()); 36 | } 37 | 38 | double TraceTrafficGenerator:: 39 | getAveragePacketSizeInBitsImpl() const { return avg_psize_; } 40 | 41 | void TraceTrafficGenerator::printConfiguration() const { 42 | std::cout << "{" 43 | << std::endl << "\ttype: " << name() << "," 44 | << std::endl << "\ttrace: " << trace_fp_ << "," 45 | << std::endl << "\tiat_ns_dist: "; 46 | 47 | iat_dist_->printConfiguration(); 48 | if (isCalibrated()) { 49 | std::cout << "," 50 | << std::endl << "\taverage_packet_size_bits: " 51 | << std::fixed << std::setprecision(2) 52 | << avg_psize_ << " bits," 53 | << std::endl << "\trate: " 54 | << getCalibratedRateInBitsPerSecond() << " bps"; 55 | } 56 | std::cout << std::endl << "}" << std::endl; 57 | } 58 | 59 | void TraceTrafficGenerator::reset() { 60 | arrival_time_ = 0; 61 | trace_ifs_.clear(); 62 | trace_ifs_.seekg(0); 63 | updateHasNewArrival(); 64 | } 65 | 66 | void TraceTrafficGenerator::updateArrivalTime() { 67 | arrival_time_ += iat_dist_->sample(); 68 | } 69 | 70 | Packet TraceTrafficGenerator::getNextArrival(const uint64_t packet_idx) { 71 | assert(has_new_arrival_); // Sanity check 72 | std::getline(trace_ifs_, line_); 73 | 74 | // Parse the packet fields 75 | auto values = split(line_, ","); 76 | assert(values.size() >= 9); // Minimum values 77 | 78 | // Extract Ethernet and IP fields 79 | uint32_t psize = (std::stoul(values[0], nullptr, 10) * 80 | kBitsPerByte); // Ethernet packet size 81 | 82 | uint32_t src_ip = std::stoul(values[1], nullptr, 16); // Src IP 83 | uint32_t dst_ip = std::stoul(values[2], nullptr, 16); // Dst IP 84 | uint16_t src_port = std::stoul(values[3], nullptr, 16); // Src port 85 | uint16_t dst_port = std::stoul(values[4], nullptr, 16); // Dst port 86 | bool is_tcp = (std::stoi(values[5], nullptr, 10) == 1); // TCP packet? 87 | 88 | // Create the packet 89 | FlowId flow_id(src_ip, dst_ip, src_port, dst_port); // Flow ID 90 | Packet packet(packet_idx, flow_id, TrafficClass::INNOCENT, psize); 91 | 92 | // Configure the TCP header, if required 93 | if (is_tcp) { 94 | int flags = std::stoi(values[6], nullptr, 10); // TCP flags 95 | uint32_t psn = std::stoul(values[7], nullptr, 10); // TCP PSN 96 | uint32_t next_psn = std::stoul(values[8], nullptr, 10); // Next PSN 97 | 98 | packet.setTCPHeader(TCPHeader( 99 | true, // Valid header? 100 | ((flags >> 2) & 0x1) == 1, // SYN flag 101 | ((flags >> 1) & 0x1) == 1, // FIN flag 102 | (flags & 0x1) == 1, // RST flag 103 | psn, // PSN 104 | next_psn // Next PSN 105 | )); 106 | } 107 | // If a job size is specified in the trace, use that 108 | if (values.size() > 9 && values[9] != "") { 109 | double jsize = std::stod(values[9]); 110 | packet.setJobSizeEstimate(jsize); 111 | } 112 | // End of trace? 113 | updateHasNewArrival(); 114 | 115 | // Update the packet's arrival time and return 116 | packet.setArriveTime(arrival_time_); 117 | return packet; 118 | } 119 | 120 | bool TraceTrafficGenerator::isCalibrated() const { 121 | return !std::isnan(avg_psize_); 122 | } 123 | 124 | void TraceTrafficGenerator::calibrate(const double avg_psize) { 125 | if (isCalibrated()) { 126 | throw std::runtime_error("Traffic-generator was already calibrated."); 127 | } 128 | avg_psize_ = avg_psize; 129 | } 130 | -------------------------------------------------------------------------------- /simulator/src/traffic/trace_trafficgen.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_TRAFFIC_TRACE_TRAFFICGEN_H 2 | #define SIMULATOR_TRAFFIC_TRACE_TRAFFICGEN_H 3 | 4 | // Library headers 5 | #include "common/distributions/constant_distribution.h" 6 | #include "trafficgen.h" 7 | 8 | // STD headers 9 | #include 10 | #include 11 | 12 | /** 13 | * Represents a trace-driven generator for innocent traffic. 14 | */ 15 | class TraceTrafficGenerator final : public TrafficGenerator { 16 | private: 17 | std::string trace_fp_; // Trace file path 18 | 19 | // TODO(natre): Currently, this is hardcoded to use constant 20 | // inter-arrival times (representing a fluid model). Ideally, 21 | // the distribution should be a user-configurable parameter. 22 | ConstantDistribution* iat_dist_ = nullptr; // IATs (ns) 23 | std::ifstream trace_ifs_; // Trace input filestream 24 | double avg_psize_ = NAN; // Average packet size (in bits) 25 | 26 | // Housekeeping 27 | std::string line_; // Scratchpad 28 | 29 | // Internal helper method 30 | void updateHasNewArrival(); 31 | 32 | // Accessors 33 | virtual uint32_t getNumFlows() const override { return 0; } 34 | virtual double getRateInBitsPerSecondImpl() const override; 35 | virtual double getAveragePacketSizeInBitsImpl() const override; 36 | 37 | public: 38 | virtual ~TraceTrafficGenerator(); 39 | explicit TraceTrafficGenerator(const std::string& trace_fp, 40 | ConstantDistribution* const iat_dist); 41 | 42 | /** 43 | * Returns the traffic-gen name. 44 | */ 45 | static std::string name() { return "trace"; } 46 | 47 | /** 48 | * Print the distribution configuration. 49 | */ 50 | virtual void printConfiguration() const override; 51 | 52 | /** 53 | * Resets the traffic-generator to its initial state. 54 | */ 55 | virtual void reset() override; 56 | 57 | /** 58 | * Invokes the implementation-specific virtual method to update the 59 | * arrival time. TODO(natre): This is a hack. Ideally, the traffic- 60 | * generator should update the next arrival time of its own accord; 61 | * however, in some instances, the arrival rate is predicated on 62 | * job size(s), which are not known at this point. 63 | */ 64 | virtual void updateArrivalTime() override; 65 | 66 | /** 67 | * Returns the next packet arrival by invoking the implementation- 68 | * specific virtual method. 69 | */ 70 | virtual Packet getNextArrival(const uint64_t packet_idx) override; 71 | 72 | /** 73 | * Returns whether the traffic-generator is calibrated. 74 | * 75 | * For some traffic-gens (eg, trace-driven), the total packet bit- 76 | * rate ultimately depends on the workload's average packet size. 77 | * Since this is only determined after running a full simulation, 78 | * the first simulation run must be a "dry-run" of the trace. We 79 | * say that the traffic-generator is "calibrated" if the average 80 | * packet size is already known (ie, this is not a dry-run). 81 | */ 82 | virtual bool isCalibrated() const override; 83 | 84 | // Calibrate the traffic-generator 85 | void calibrate(const double avg_psize); 86 | }; 87 | 88 | #endif // SIMULATOR_TRAFFIC_TRACE_TRAFFICGEN_H 89 | -------------------------------------------------------------------------------- /simulator/src/traffic/trafficgen.cpp: -------------------------------------------------------------------------------- 1 | #include "trafficgen.h" 2 | 3 | // STD headers 4 | #include 5 | 6 | /** 7 | * TrafficGenerator implementation. 8 | */ 9 | double TrafficGenerator::getCalibratedRateInBitsPerSecond() const { 10 | if (!isCalibrated()) { 11 | throw std::runtime_error("TrafficGenerator is not calibrated"); 12 | } 13 | return getRateInBitsPerSecondImpl(); 14 | } 15 | 16 | double TrafficGenerator::getCalibratedAveragePacketSizeInBits() const { 17 | if (!isCalibrated()) { 18 | throw std::runtime_error("TrafficGenerator is not calibrated"); 19 | } 20 | return getAveragePacketSizeInBitsImpl(); 21 | } 22 | -------------------------------------------------------------------------------- /simulator/src/traffic/trafficgen.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_TRAFFIC_TRAFFICGEN_H 2 | #define SIMULATOR_TRAFFIC_TRAFFICGEN_H 3 | 4 | // Library headers 5 | #include "packet/packet.h" 6 | 7 | // STD headers 8 | #include 9 | #include 10 | 11 | /** 12 | * Base class representing a traffic generator. 13 | */ 14 | class TrafficGenerator { 15 | protected: 16 | const std::string kType; // Traffic-gen type 17 | bool has_new_arrival_ = true; // Can generate more arrivals? 18 | double arrival_time_ = 0; // Arrival time for the next packet 19 | 20 | // Accessors 21 | virtual double getRateInBitsPerSecondImpl() const = 0; 22 | virtual double getAveragePacketSizeInBitsImpl() const = 0; 23 | 24 | public: 25 | explicit TrafficGenerator(const std::string type) : kType(type) {} 26 | virtual ~TrafficGenerator() {} 27 | 28 | // Accessors 29 | virtual uint32_t getNumFlows() const = 0; 30 | double getCalibratedRateInBitsPerSecond() const; 31 | double getCalibratedAveragePacketSizeInBits() const; 32 | bool hasNewArrival() const { return has_new_arrival_; } 33 | double getNextArrivalTime() const { return arrival_time_; } 34 | 35 | /** 36 | * Returns the traffic-generator type. 37 | */ 38 | const std::string& type() const { return kType; } 39 | 40 | /** 41 | * Print the distribution configuration. 42 | */ 43 | virtual void printConfiguration() const = 0; 44 | 45 | /** 46 | * Resets the traffic-generator to its initial state. 47 | */ 48 | virtual void reset() = 0; 49 | 50 | /** 51 | * Invokes the implementation-specific virtual method to update the 52 | * arrival time. TODO(natre): This is a hack. Ideally, the traffic- 53 | * generator should update the next arrival time of its own accord; 54 | * however, in some instances, the arrival rate is predicated on 55 | * job size(s), which are not known at this point. 56 | */ 57 | virtual void updateArrivalTime() = 0; 58 | 59 | /** 60 | * Returns the next packet arrival by invoking the implementation- 61 | * specific virtual method. 62 | */ 63 | virtual Packet getNextArrival(const uint64_t packet_idx) = 0; 64 | 65 | /** 66 | * Returns whether the traffic-generator is calibrated. 67 | * 68 | * For some traffic-gens (eg, trace-driven), the total packet bit- 69 | * rate ultimately depends on the workload's average packet size. 70 | * Since this is only determined after running a full simulation, 71 | * the first simulation run must be a "dry-run" of the trace. We 72 | * say that the traffic-generator is "calibrated" if the average 73 | * packet size is already known (ie, this is not a dry-run). 74 | */ 75 | virtual bool isCalibrated() const = 0; 76 | }; 77 | 78 | #endif // SIMULATOR_TRAFFIC_TRAFFICGEN_H 79 | -------------------------------------------------------------------------------- /simulator/src/traffic/trafficgen_factory.cpp: -------------------------------------------------------------------------------- 1 | #include "trafficgen_factory.h" 2 | 3 | // Library headers 4 | #include "common/distributions/distribution_factory.h" 5 | #include "common/macros.h" 6 | #include "synthetic_trafficgen.h" 7 | #include "trace_trafficgen.h" 8 | 9 | // STD headers 10 | #include 11 | #include 12 | 13 | TrafficGenerator* TrafficGeneratorFactory::generate( 14 | const bool is_dry_run, const TrafficClass tg_type, 15 | const libconfig::Setting& tg_config, 16 | const uint32_t fid_start_offset) { 17 | TrafficGenerator* tg = nullptr; 18 | 19 | // Default inter-arrival time for dry-runs 20 | const double kDryRunIATInNs = 1000; 21 | 22 | std::string type; // Trafficgen type 23 | if (!tg_config.lookupValue("type", type)) { 24 | throw std::runtime_error("No traffic-gen type specified."); 25 | } 26 | // Trace-driven traffic-generator 27 | else if (type == TraceTrafficGenerator::name()) { 28 | if (tg_type == TrafficClass::ATTACK) { 29 | throw std::runtime_error("Adversarial traffic-generators must " 30 | "be synthetic (not trace-driven)."); 31 | } 32 | std::string trace_fp; // Trace file-path 33 | if (!tg_config.lookupValue("trace_fp", trace_fp)) { 34 | throw std::runtime_error("Must specify 'trace_fp' for " 35 | "trace-driven traffic-gens."); 36 | } 37 | double avg_psize = NAN, rate = NAN; // Average psize (b), rate (bps) 38 | tg_config.lookupValue("average_packet_size_bits", avg_psize); 39 | tg_config.lookupValue("rate_bps", rate); 40 | 41 | // Compute the average IAT 42 | bool is_calibrated = false; 43 | double iat_ns = kDryRunIATInNs; 44 | if (!std::isnan(avg_psize) && !std::isnan(rate) && !is_dry_run) { 45 | // If both the average packet size and rate parameters are 46 | // specified, then the traffic-generator can be calibrated. 47 | // Else, the simulator will run in "dry-run" mode. 48 | iat_ns = (kNanosecsPerSec * avg_psize) / rate; 49 | is_calibrated = true; 50 | } 51 | 52 | ConstantDistribution* iat_dist = new ConstantDistribution(iat_ns); 53 | auto trace_tg = new TraceTrafficGenerator(trace_fp, iat_dist); 54 | if (is_calibrated) { trace_tg->calibrate(avg_psize); } 55 | tg = trace_tg; 56 | } 57 | // Synthetic traffic-generator 58 | else if (type == SyntheticTrafficGenerator::name()) { 59 | // Parse the flow count 60 | uint32_t nflows = 1; 61 | tg_config.lookupValue("num_flows", nflows); 62 | 63 | // Innocent traffic 64 | if (tg_type == TrafficClass::INNOCENT) { 65 | if (!tg_config.exists("packet_size_bits_dist")) { 66 | throw std::runtime_error("Must specify 'packet_size_bits_dist' " 67 | "for synthetic traffic-generators."); 68 | } 69 | // Parse packet-size distribution 70 | libconfig::Setting& psize_config = tg_config["packet_size_bits_dist"]; 71 | auto psize_dist = DistributionFactory::generate(psize_config); 72 | const auto avg_psize = psize_dist->getSampleStats().getMean(); 73 | 74 | // Parse the rate (bps) 75 | double rate = NAN; 76 | tg_config.lookupValue("rate_bps", rate); 77 | 78 | // Compute the average IAT 79 | bool is_calibrated = false; 80 | double iat_ns = kDryRunIATInNs; 81 | if (!std::isnan(rate) && !is_dry_run) { 82 | is_calibrated = true; 83 | iat_ns = (kNanosecsPerSec * avg_psize) / rate; 84 | } 85 | 86 | ConstantDistribution* iat_dist = new ConstantDistribution(iat_ns); 87 | auto synthetic_tg = new InnocentTrafficGenerator( 88 | nflows, iat_dist, psize_dist); 89 | 90 | if (is_calibrated) {synthetic_tg->calibrate(rate); } 91 | tg = synthetic_tg; 92 | } 93 | // Attack traffic 94 | else { 95 | // Parse the rate (bps) 96 | double rate = 0; // Default (no traffic) 97 | tg_config.lookupValue("rate_bps", rate); 98 | 99 | // Compute the average IAT 100 | double iat_ns = kDblPosInfty; 101 | uint32_t attack_psize_bits = 0; 102 | double attack_jsize_ns = kInvalidJobSize; 103 | 104 | if (rate > 0 && !is_dry_run) { 105 | if (!tg_config.lookupValue("job_size_ns", attack_jsize_ns)) { 106 | throw std::runtime_error("Must specify 'job_size_ns' for " 107 | "attack traffic-generators when " 108 | "not running in dry-run mode."); 109 | } 110 | if (!tg_config.lookupValue("packet_size_bits", attack_psize_bits)) { 111 | throw std::runtime_error("Must specify 'packet_size_bits' for " 112 | "attack traffic-generators when not " 113 | "running in dry-run mode."); 114 | } 115 | iat_ns = (kNanosecsPerSec * attack_psize_bits) / rate; 116 | } 117 | else if (rate > 0) { 118 | std::cout << "Warning: In dry-run mode, but adversarial rate is " 119 | << "non-zero. No attack traffic will be generated." 120 | << std::endl; 121 | } 122 | 123 | ConstantDistribution* iat_dist = new ConstantDistribution(iat_ns); 124 | auto attack_tg = new AttackTrafficGenerator(nflows, fid_start_offset, iat_dist, 125 | attack_psize_bits, attack_jsize_ns); 126 | attack_tg->calibrate(rate); 127 | tg = attack_tg; 128 | } 129 | } 130 | // Unknown traffic-generator 131 | else { throw std::runtime_error( 132 | "Unknown traffic-gen type: " + type + "."); } 133 | 134 | // Perform validation. The traffic-generator 135 | // may be uncalibrated ONLY in dry-run mode. 136 | if (!is_dry_run && !tg->isCalibrated()) { 137 | throw std::runtime_error("Traffic-generator must be calibrated (have " 138 | "a valid rate and average packet size) when " 139 | "not running in dry-run mode."); 140 | } 141 | return tg; 142 | } 143 | -------------------------------------------------------------------------------- /simulator/src/traffic/trafficgen_factory.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATOR_TRAFFIC_TRAFFICGEN_FACTORY_H 2 | #define SIMULATOR_TRAFFIC_TRAFFICGEN_FACTORY_H 3 | 4 | // Library headers 5 | #include "trafficgen.h" 6 | 7 | // Libconfig 8 | #include 9 | 10 | /** 11 | * Factory class for instantiating traffic generators. 12 | */ 13 | class TrafficGeneratorFactory final { 14 | public: 15 | /** 16 | * Returns a traffic-gen corresponding 17 | * to the parameterized configuration. 18 | */ 19 | static TrafficGenerator* 20 | generate(const bool is_dry_run, 21 | const TrafficClass tg_type, 22 | const libconfig::Setting& tg_config, 23 | const uint32_t fid_start_offset = 0); 24 | }; 25 | 26 | #endif // SIMULATOR_TRAFFIC_TRAFFICGEN_FACTORY_H 27 | --------------------------------------------------------------------------------