/// @file /// @author rfree (current maintainer in monero.cc project) /// @brief implementaion for throttling of connection (count and rate-limit speed etc) // Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* rfree: implementation for throttle details */ #include #include #include #include #include #include "syncobj.h" #include "net/net_utils_base.h" #include "misc_log_ex.h" #include #include "misc_language.h" #include #include #include #include #include #include "net/abstract_tcp_server2.h" // TODO: #include "net/network_throttle-detail.hpp" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.throttle" // ################################################################################################ // ################################################################################################ // the "header part". Not separeted out for .hpp because point of this modification is // to rebuild just 1 translation unit while working on this code. // (But maybe common parts will be separated out later though - if needed) // ################################################################################################ // ################################################################################################ namespace epee { namespace net_utils { /* ============================================================================ */ class connection_basic_pimpl { public: connection_basic_pimpl(const std::string &name); static int m_default_tos; network_throttle_bw m_throttle; // per-perr critical_section m_throttle_lock; void _packet(size_t packet_size, int phase, int q_len); // execute a sleep ; phase is not really used now(?) could be used for different kinds of sleep e.g. direct/queue write }; } // namespace } // namespace // ################################################################################################ // ################################################################################################ // The implementation part // ################################################################################################ // ################################################################################################ namespace epee { namespace net_utils { // ================================================================================================ // network_throttle // ================================================================================================ network_throttle::~network_throttle() { } network_throttle::packet_info::packet_info() : m_size(0) { } network_throttle::network_throttle(const std::string &nameshort, const std::string &name, int window_size) : m_window_size( (window_size==-1) ? 10 : window_size ), m_history( m_window_size ), m_nameshort(nameshort) { set_name(name); m_network_add_cost = 128; m_network_minimal_segment = 256; m_network_max_segment = 1024*1024; m_start_time = 0; m_any_packet_yet = false; m_slot_size = 1.0; // hard coded in few places m_target_speed = 16 * 1024; // other defaults are probably defined in the command-line parsing code when this class is used e.g. as main global throttle m_last_sample_time = 0; m_history.resize(m_window_size); m_total_packets = 0; m_total_bytes = 0; } void network_throttle::set_name(const std::string &name) { m_name = name; } void network_throttle::set_target_speed( network_speed_kbps target ) { m_target_speed = target * 1024; MINFO("Setting LIMIT: " << target << " kbps"); } network_speed_kbps network_throttle::get_target_speed() { return m_target_speed / 1024; } void network_throttle::tick() { double time_now = get_time_seconds(); if (!m_any_packet_yet) m_start_time = time_now; // starting now network_time_seconds current_sample_time_slot = time_to_slot( time_now ); // T=13.7 --> 13 (for 1-second smallwindow) network_time_seconds last_sample_time_slot = time_to_slot( m_last_sample_time ); // moving to next position, and filling gaps // !! during this loop the m_last_sample_time and last_sample_time_slot mean the variable moved in +1 // TODO optimize when moving few slots at once while ( (!m_any_packet_yet) || (last_sample_time_slot < current_sample_time_slot)) { _dbg3("Moving counter buffer by 1 second " << last_sample_time_slot << " < " << current_sample_time_slot << " (last time " << m_last_sample_time<<")"); // rotate buffer m_history.push_front(packet_info()); if (! m_any_packet_yet) { m_last_sample_time = time_now; } m_last_sample_time += 1; last_sample_time_slot = time_to_slot( m_last_sample_time ); // increase and recalculate time, time slot m_any_packet_yet=true; } m_last_sample_time = time_now; // the real exact last time } void network_throttle::handle_trafic_exact(size_t packet_size) { _handle_trafic_exact(packet_size, packet_size); } void network_throttle::_handle_trafic_exact(size_t packet_size, size_t orginal_size) { tick(); calculate_times_struct cts ; calculate_times(packet_size, cts , false, -1); calculate_times_struct cts2; calculate_times(packet_size, cts2, false, 5); m_history.front().m_size += packet_size; m_total_packets++; m_total_bytes += packet_size; std::ostringstream oss; oss << "["; for (auto sample: m_history) oss << sample.m_size << " "; oss << "]" << std::ends; std::string history_str = oss.str(); MTRACE("Throttle " << m_name << ": packet of ~"< lock(mutex); { std::fstream file; file.open(filename.c_str(), std::ios::app | std::ios::out ); file.precision(6); if(!file.is_open()) _warn("Can't open file " << filename); file << static_cast(time) << " " << static_cast(size/1024) << "\n"; file.close(); } } // fine tune this to decide about sending speed: network_time_seconds network_throttle::get_sleep_time(size_t packet_size) const { double D2=0; calculate_times_struct cts = { 0, 0, 0, 0}; calculate_times(packet_size, cts, true, m_window_size); D2=cts.delay; return D2; } // MAIN LOGIC: void network_throttle::calculate_times(size_t packet_size, calculate_times_struct &cts, bool dbg, double force_window) const { const double the_window_size = std::max( (double)m_window_size , ((force_window>0) ? force_window : m_window_size) ); if (!m_any_packet_yet) { cts.window=0; cts.average=0; cts.delay=0; cts.recomendetDataSize = m_network_minimal_segment; // should be overrided by caller anyway return ; // no packet yet, I can not decide about sleep time } network_time_seconds window_len = (the_window_size-1) * m_slot_size ; // -1 since current slot is not finished window_len += (m_last_sample_time - time_to_slot(m_last_sample_time)); // add the time for current slot e.g. 13.7-13 = 0.7 auto time_passed = get_time_seconds() - m_start_time; cts.window = std::max( std::min( window_len , time_passed ) , m_slot_size ) ; // window length resulting from size of history but limited by how long ago history was started, // also at least slot size (e.g. 1 second) to not be ridiculous // window_len e.g. 5.7 because takes into account current slot time size_t Epast = 0; // summ of traffic till now for (auto sample : m_history) Epast += sample.m_size; const size_t E = Epast; const size_t Enow = Epast + packet_size ; // including the data we're about to send now const double M = m_target_speed; // max const double D1 = (Epast - M*cts.window) / M; // delay - how long to sleep to get back to target speed const double D2 = (Enow - M*cts.window) / M; // delay - how long to sleep to get back to target speed (including current packet) cts.delay = (D1*0.80 + D2*0.20); // finall sleep depends on both with/without current packet // update_overheat(); cts.average = Epast/cts.window; // current avg. speed (for info) if (Epast <= 0) { if (cts.delay>=0) cts.delay = 0; // no traffic in history so we will not wait } double Wgood=-1; { // how much data we recommend now to download Wgood = the_window_size + 1; cts.recomendetDataSize = M*cts.window - E; } if (dbg) { std::ostringstream oss; oss << "["; for (auto sample: m_history) oss << sample.m_size << " "; oss << "]" << std::ends; std::string history_str = oss.str(); MTRACE((cts.delay > 0 ? "SLEEP" : "") << "dbg " << m_name << ": " << "speed is A=" << std::setw(8) <