diff --git a/.gitignore b/.gitignore index 9279f68..042c432 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,35 @@ cmake-build-debug/ cmake-build-linux/ # Visual Studio 2015/2017 cache/options directory -.vs/ \ No newline at end of file +.vs/ +cmake_install.cmake +CMakeCache.txt +install_manifest.txt +Makefile +SpeedTest +SpeedTestConfig.h +CMakeFiles/cmake.check_cache +CMakeFiles/CMakeDirectoryInformation.cmake +CMakeFiles/CMakeOutput.log +CMakeFiles/Makefile.cmake +CMakeFiles/Makefile2 +CMakeFiles/progress.marks +CMakeFiles/TargetDirectories.txt +CMakeFiles/3.16.3/CMakeCCompiler.cmake +CMakeFiles/3.16.3/CMakeCXXCompiler.cmake +CMakeFiles/3.16.3/CMakeDetermineCompilerABI_C.bin +CMakeFiles/3.16.3/CMakeDetermineCompilerABI_CXX.bin +CMakeFiles/3.16.3/CMakeSystem.cmake +CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c +CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp +CMakeFiles/SpeedTest.dir/build.make +CMakeFiles/SpeedTest.dir/cmake_clean.cmake +CMakeFiles/SpeedTest.dir/CXX.includecache +CMakeFiles/SpeedTest.dir/depend.internal +CMakeFiles/SpeedTest.dir/depend.make +CMakeFiles/SpeedTest.dir/DependInfo.cmake +CMakeFiles/SpeedTest.dir/flags.make +CMakeFiles/SpeedTest.dir/link.txt +CMakeFiles/SpeedTest.dir/progress.make +CMakeFiles/Progress/1 +CMakeFiles/Progress/count.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 4383d29..0571e1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.7) project(SpeedTest) set (SpeedTest_VERSION_MAJOR 1) -set (SpeedTest_VERSION_MINOR 14) +set (SpeedTest_VERSION_MINOR 15) set (SpeedTest_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}) set (SpeedTest_SYSTEM ${CMAKE_SYSTEM}) diff --git a/CmdOptions.h b/CmdOptions.h index 79ddf3a..f9f9995 100644 --- a/CmdOptions.h +++ b/CmdOptions.h @@ -7,6 +7,7 @@ #include enum OutputType { verbose, text, json }; +enum LineType { automatic, slow, narrow, broad, fiber, gigasym }; typedef struct program_options_t { @@ -17,6 +18,7 @@ typedef struct program_options_t { bool share = false; std::string selected_server = ""; OutputType output_type = OutputType::verbose; + LineType line_type = LineType::automatic; } ProgramOptions; static struct option CmdLongOptions[] = { @@ -27,6 +29,7 @@ static struct option CmdLongOptions[] = { {"share", no_argument, 0, 's' }, {"test-server", required_argument, 0, 't' }, {"output", required_argument, 0, 'o' }, + {"line-type", required_argument, 0, 'i' }, {0, 0, 0, 0 } }; @@ -68,6 +71,26 @@ bool ParseOptions(const int argc, const char **argv, ProgramOptions& options){ return false; } + break; + case 'i': + if (strcmp(optarg, "auto") == 0) + options.line_type = LineType::automatic; + else if (strcmp(optarg, "slow") == 0) + options.line_type = LineType::slow; + else if (strcmp(optarg, "narrow") == 0) + options.line_type = LineType::narrow; + else if (strcmp(optarg, "broad") == 0) + options.line_type = LineType::broad; + else if (strcmp(optarg, "fiber") == 0) + options.line_type = LineType::fiber; + else if (strcmp(optarg, "gigasym") == 0) + options.line_type = LineType::gigasym; + else { + std::cerr << "Unsupported line type " << optarg << std::endl; + std::cerr << "Supported line type: auto, slow, narrow, broad, fiber" < #include "SpeedTest.h" #include "MD5Util.h" +#include #include SpeedTest::SpeedTest(float minServerVersion): @@ -97,19 +98,19 @@ bool SpeedTest::uploadSpeed(const ServerInfo &server, const TestConfig &config, return true; } -const long &SpeedTest::latency() { +const double &SpeedTest::latency() { return mLatency; } -bool SpeedTest::jitter(const ServerInfo &server, long& result, const int sample) { +bool SpeedTest::jitter(const ServerInfo &server, double& result, const int sample) { auto client = SpeedTestClient(server); double current_jitter = 0; - long previous_ms = LONG_MAX; + double previous_ms = DBL_MAX; if (client.connect()){ for (int i = 0; i < sample; i++){ - long ms = 0; + double ms = 0; if (client.ping(ms)){ - if (previous_ms == LONG_MAX) { + if (previous_ms == DBL_MAX) { previous_ms = ms; } else { current_jitter += std::abs(previous_ms - ms); @@ -121,21 +122,21 @@ bool SpeedTest::jitter(const ServerInfo &server, long& result, const int sample) return false; } - result = (long) std::floor(current_jitter / sample); + result = current_jitter / sample; return true; } bool SpeedTest::share(const ServerInfo& server, std::string& image_url) { std::stringstream hash; - hash << std::setprecision(0) << std::fixed << mLatency + hash << std::setprecision(2) << std::fixed << (mLatency / 1000) << "-" << std::setprecision(2) << std::fixed << (mUploadSpeed * 1000) << "-" << std::setprecision(2) << std::fixed << (mDownloadSpeed * 1000) << "-" << SPEED_TEST_API_KEY; std::string hex_digest = MD5Util::hexDigest(hash.str()); std::stringstream post_data; post_data << "download=" << std::setprecision(2) << std::fixed << (mDownloadSpeed * 1000) << "&"; - post_data << "ping=" << std::setprecision(0) << std::fixed << mLatency << "&"; + post_data << "ping=" << std::setprecision(2) << std::fixed << (mLatency / 1000) << "&"; post_data << "upload=" << std::setprecision(2) << std::fixed << (mUploadSpeed * 1000) << "&"; post_data << "pingselect=1&"; post_data << "recommendedserverid=" << server.id << "&"; @@ -186,11 +187,11 @@ double SpeedTest::execute(const ServerInfo &server, const TestConfig &config, co auto start = std::chrono::steady_clock::now(); std::vector partial_results; while (curr_size < max_size){ - long op_time = 0; + double op_time = 0; if ((spClient.*pfunc)(curr_size, config.buff_size, op_time)) { total_size += curr_size; total_time += op_time; - double metric = (curr_size * 8) / (static_cast(op_time) / 1000); + double metric = (curr_size * 8) / (op_time / 1000); partial_results.push_back(metric); if (cb) cb(true); @@ -236,7 +237,7 @@ double SpeedTest::execute(const ServerInfo &server, const TestConfig &config, co workers.clear(); - return overall_speed / 1000 / 1000; + return overall_speed / 1024 / 1024; } template @@ -297,7 +298,8 @@ CURL *SpeedTest::curl_setup(CURL *handler) { if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeFunc) == CURLE_OK && curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L) == CURLE_OK && curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) == CURLE_OK - && curl_easy_setopt(curl, CURLOPT_USERAGENT, SPEED_TEST_USER_AGENT) == CURLE_OK){ + && curl_easy_setopt(curl, CURLOPT_USERAGENT, SPEED_TEST_USER_AGENT) == CURLE_OK + && curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "br, gzip, deflate") == CURLE_OK) { return curl; } else { curl_easy_cleanup(handler); @@ -492,12 +494,12 @@ bool SpeedTest::fetchServers(const std::string& url, std::vector& ta return true; } -const ServerInfo SpeedTest::findBestServerWithin(const std::vector &serverList, long &latency, +const ServerInfo SpeedTest::findBestServerWithin(const std::vector &serverList, double &latency, const int sample_size, std::function cb) { int i = sample_size; ServerInfo bestServer = serverList[0]; - latency = INT_MAX; + latency = DBL_MAX; for (auto &server : serverList){ auto client = SpeedTestClient(server); @@ -513,7 +515,7 @@ const ServerInfo SpeedTest::findBestServerWithin(const std::vector & continue; } - long current_latency = LONG_MAX; + double current_latency = DBL_MAX; if (testLatency(client, 20, current_latency)){ if (current_latency < latency){ latency = current_latency; @@ -532,12 +534,12 @@ const ServerInfo SpeedTest::findBestServerWithin(const std::vector & return bestServer; } -bool SpeedTest::testLatency(SpeedTestClient &client, const int sample_size, long &latency) { +bool SpeedTest::testLatency(SpeedTestClient &client, const int sample_size, double &latency) { if (!client.connect()){ return false; } - latency = INT_MAX; - long temp_latency = 0; + latency = DBL_MAX; + double temp_latency = 0; for (int i = 0; i < sample_size; i++){ if (client.ping(temp_latency)){ if (temp_latency < latency){ @@ -549,4 +551,3 @@ bool SpeedTest::testLatency(SpeedTestClient &client, const int sample_size, long } return true; } - diff --git a/SpeedTest.h b/SpeedTest.h index 9b8c8ce..efb6ddf 100644 --- a/SpeedTest.h +++ b/SpeedTest.h @@ -22,7 +22,7 @@ #include "DataTypes.h" class SpeedTestClient; -typedef bool (SpeedTestClient::*opFn)(const long size, const long chunk_size, long &millisec); +typedef bool (SpeedTestClient::*opFn)(const long size, const long chunk_size, double &millisec); typedef void (*progressFn)(bool success); @@ -38,15 +38,15 @@ class SpeedTest { const std::vector& serverList(); const ServerInfo bestServer(int sample_size = 5, std::function cb = nullptr); bool setServer(ServerInfo& server); - const long &latency(); + const double &latency(); bool downloadSpeed(const ServerInfo& server, const TestConfig& config, double& result, std::function cb = nullptr); bool uploadSpeed(const ServerInfo& server, const TestConfig& config, double& result, std::function cb = nullptr); - bool jitter(const ServerInfo& server, long& result, int sample = 40); + bool jitter(const ServerInfo& server, double& result, int sample = 40); bool share(const ServerInfo& server, std::string& image_url); private: bool fetchServers(const std::string& url, std::vector& target, int &http_code); - bool testLatency(SpeedTestClient& client, int sample_size, long& latency); - const ServerInfo findBestServerWithin(const std::vector& serverList, long& latency, int sample_size = 5, std::function cb = nullptr); + bool testLatency(SpeedTestClient& client, int sample_size, double& latency); + const ServerInfo findBestServerWithin(const std::vector& serverList, double& latency, int sample_size = 5, std::function cb = nullptr); static CURL* curl_setup(CURL* curl = nullptr); static size_t writeFunc(void* buf, size_t size, size_t nmemb, void* userp); static ServerInfo processServerXMLNode(xmlTextReaderPtr reader); @@ -58,7 +58,7 @@ class SpeedTest { IPInfo mIpInfo; std::vector mServerList; - long mLatency; + double mLatency; double mUploadSpeed; double mDownloadSpeed; float mMinSupportedServer; diff --git a/SpeedTestClient.cpp b/SpeedTestClient.cpp index 39ffc14..6e58016 100644 --- a/SpeedTestClient.cpp +++ b/SpeedTestClient.cpp @@ -63,7 +63,7 @@ void SpeedTestClient::close() { } // It executes PING command -bool SpeedTestClient::ping(long &millisec) { +bool SpeedTestClient::ping(double &millisec) { if (!mSocketFd) return false; @@ -80,7 +80,8 @@ bool SpeedTestClient::ping(long &millisec) { if (SpeedTestClient::readLine(mSocketFd, reply)){ if (reply.substr(0, 5) == "PONG "){ auto stop = std::chrono::steady_clock::now(); - millisec = std::chrono::duration_cast(stop - start).count(); + double microsec = std::chrono::duration_cast(stop - start).count(); + millisec = microsec / 1000; return true; } } @@ -90,7 +91,7 @@ bool SpeedTestClient::ping(long &millisec) { } // It executes DOWNLOAD command -bool SpeedTestClient::download(const long size, const long chunk_size, long &millisec) { +bool SpeedTestClient::download(const long size, const long chunk_size, double &millisec) { std::stringstream cmd; cmd << "DOWNLOAD " << size; @@ -116,13 +117,14 @@ bool SpeedTestClient::download(const long size, const long chunk_size, long &mil } auto stop = std::chrono::steady_clock::now(); - millisec = std::chrono::duration_cast(stop - start).count(); + double microsec = std::chrono::duration_cast(stop - start).count(); + millisec = microsec / 1000; delete[] buff; return true; } // It executes UPLOAD command -bool SpeedTestClient::upload(const long size, const long chunk_size, long &millisec) { +bool SpeedTestClient::upload(const long size, const long chunk_size, double &millisec) { std::stringstream cmd; cmd << "UPLOAD " << size << "\n"; auto cmd_len = cmd.str().length(); @@ -170,7 +172,8 @@ bool SpeedTestClient::upload(const long size, const long chunk_size, long &milli std::stringstream ss; ss << "OK " << size << " "; - millisec = std::chrono::duration_cast(stop - start).count(); + double microsec = std::chrono::duration_cast(stop - start).count(); + millisec = microsec / 1000; delete[] buff; return reply.substr(0, ss.str().length()) == ss.str(); diff --git a/SpeedTestClient.h b/SpeedTestClient.h index 141ecf4..d91d44b 100644 --- a/SpeedTestClient.h +++ b/SpeedTestClient.h @@ -22,9 +22,9 @@ class SpeedTestClient { ~SpeedTestClient(); bool connect(); - bool ping(long &millisec); - bool upload(long size, long chunk_size, long &millisec); - bool download(long size, long chunk_size, long &millisec); + bool ping(double &millisec); + bool upload(long size, long chunk_size, double &millisec); + bool download(long size, long chunk_size, double &millisec); float version(); const std::pair hostport(); void close(); @@ -39,5 +39,5 @@ class SpeedTestClient { static bool writeLine(int& fd, const std::string& buffer); }; -typedef bool (SpeedTestClient::*opFn)(const long size, const long chunk_size, long &millisec); +typedef bool (SpeedTestClient::*opFn)(const long size, const long chunk_size, double &millisec); #endif //SPEEDTEST_SPEEDTESTCLIENT_H diff --git a/TestConfigTemplate.h b/TestConfigTemplate.h index 3d0acce..c96518b 100644 --- a/TestConfigTemplate.h +++ b/TestConfigTemplate.h @@ -99,6 +99,28 @@ const TestConfig fiberConfigUpload = { "Fiber / Lan line type detected: profile selected fiber" }; +// The following configuration was deduced based on actual traffic monitoring on my Mikrotik router + +const TestConfig gigaSymConfigDownload = { + 5000000, // start_size + 120000000, // max_size + 950000, // inc_size + 65536, // buff_size + 15000, // min_test_time_ms + 6, // concurrency + "Gigabit symmetric line type detected: profile selected gigasym" +}; + +const TestConfig gigaSymConfigUpload = { + 5000000, // start_size + 70000000, // max_size + 0, // inc_size + 65536, // buff_size + 15000, // min_test_time_ms + 4, // concurrency + "Gigabit symmetric line type detected: profile selected gigasym" +}; + void testConfigSelector(const double preSpeed, TestConfig& uploadConfig, TestConfig& downloadConfig){ uploadConfig = slowConfigUpload; downloadConfig = slowConfigDownload; @@ -110,9 +132,12 @@ void testConfigSelector(const double preSpeed, TestConfig& uploadConfig, TestCon } else if (preSpeed > 30 && preSpeed < 150) { downloadConfig = broadbandConfigDownload; uploadConfig = broadbandConfigUpload; - } else if (preSpeed >= 150) { + } else if (preSpeed > 150 && preSpeed < 450) { downloadConfig = fiberConfigDownload; uploadConfig = fiberConfigUpload; + } else if (preSpeed >= 450) { + downloadConfig = gigaSymConfigDownload; + uploadConfig = gigaSymConfigUpload; } } diff --git a/main.cpp b/main.cpp index 8582921..90bdfe4 100644 --- a/main.cpp +++ b/main.cpp @@ -16,7 +16,8 @@ void banner(){ void usage(const char* name){ std::cerr << "Usage: " << name << " "; std::cerr << " [--latency] [--quality] [--download] [--upload] [--share] [--help]\n" - " [--test-server host:port] [--output verbose|text|json]\n"; + " [--test-server host:port] [--output verbose|text|json]\n" + " [--line-type auto|slow|narrow|broad|fiber]\n"; std::cerr << "optional arguments:" << std::endl; std::cerr << " --help Show this message and exit\n"; std::cerr << " --latency Perform latency test only\n"; @@ -27,6 +28,7 @@ void usage(const char* name){ std::cerr << " --test-server host:port Run speed test against a specific server\n"; std::cerr << " --quality-server host:port Run line quality test against a specific server\n"; std::cerr << " --output verbose|text|json Set output type. Default: verbose\n"; + std::cerr << " --output auto|slow|narrow|broad|fiber Set line type. Default: auto\n"; } int main(const int argc, const char **argv) { @@ -163,7 +165,7 @@ int main(const int argc, const char **argv) { std::cout << sp.latency() << "\","; } - long jitter = 0; + double jitter = 0; if (programOptions.output_type == OutputType::verbose) std::cout << "Jitter: " << std::flush; if (sp.jitter(serverInfo, jitter)){ @@ -186,26 +188,48 @@ int main(const int argc, const char **argv) { return EXIT_SUCCESS; } + TestConfig uploadConfig; + TestConfig downloadConfig; - if (programOptions.output_type == OutputType::verbose) - std::cout << "Determine line type (" << preflightConfigDownload.concurrency << ") " << std::flush; - double preSpeed = 0; - if (!sp.downloadSpeed(serverInfo, preflightConfigDownload, preSpeed, [&programOptions](bool success){ + if (programOptions.line_type == LineType::automatic) { if (programOptions.output_type == OutputType::verbose) - std::cout << (success ? '.' : '*') << std::flush; - })){ - std::cerr << "Pre-flight check failed." << std::endl; - if (programOptions.output_type == OutputType::json) - std::cout << "\"error\":\"pre-flight check failed\"}" << std::endl; - return EXIT_FAILURE; - } - - if (programOptions.output_type == OutputType::verbose) - std::cout << std::endl; + std::cout << "Determine line type (" << preflightConfigDownload.concurrency << ") " << std::flush; + double preSpeed = 0; + if (!sp.downloadSpeed(serverInfo, preflightConfigDownload, preSpeed, [&programOptions](bool success){ + if (programOptions.output_type == OutputType::verbose) + std::cout << (success ? '.' : '*') << std::flush; + })){ + std::cerr << "Pre-flight check failed." << std::endl; + if (programOptions.output_type == OutputType::json) + std::cout << "\"error\":\"pre-flight check failed\"}" << std::endl; + return EXIT_FAILURE; + } - TestConfig uploadConfig; - TestConfig downloadConfig; - testConfigSelector(preSpeed, uploadConfig, downloadConfig); + if (programOptions.output_type == OutputType::verbose) + std::cout << std::endl; + + testConfigSelector(preSpeed, uploadConfig, downloadConfig); + } + else if (programOptions.line_type == LineType::slow) { + uploadConfig = slowConfigUpload; + downloadConfig = slowConfigDownload; + } + else if (programOptions.line_type == LineType::narrow) { + uploadConfig = narrowConfigUpload; + downloadConfig = narrowConfigDownload; + } + else if (programOptions.line_type == LineType::broad) { + uploadConfig = broadbandConfigUpload; + downloadConfig = broadbandConfigDownload; + } + else if (programOptions.line_type == LineType::fiber) { + uploadConfig = fiberConfigUpload; + downloadConfig = fiberConfigDownload; + } + else if (programOptions.line_type == LineType::gigasym) { + uploadConfig = gigaSymConfigUpload; + downloadConfig = gigaSymConfigDownload; + } if (programOptions.output_type == OutputType::verbose) std::cout << downloadConfig.label << std::endl; @@ -236,7 +260,7 @@ int main(const int argc, const char **argv) { } else if (programOptions.output_type == OutputType::json) { std::cout << "\"download\":\""; std::cout << std::fixed; - std::cout << (downloadSpeed*1000*1000) << "\","; + std::cout << (downloadSpeed) << "\","; } } else { std::cerr << "Download test failed." << std::endl; @@ -274,7 +298,7 @@ int main(const int argc, const char **argv) { } else if (programOptions.output_type == OutputType::json) { std::cout << "\"upload\":\""; std::cout << std::fixed; - std::cout << (uploadSpeed*1000*1000) << "\","; + std::cout << (uploadSpeed) << "\","; } } else {