| /* |
| * Copyright 2015 Google Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "speedtest.h" |
| |
| #include <assert.h> |
| #include <chrono> |
| #include <cstring> |
| #include <limits> |
| #include <random> |
| #include <thread> |
| #include <iomanip> |
| |
| #include "errors.h" |
| #include "utils.h" |
| |
| namespace speedtest { |
| |
| Speedtest::Speedtest(const Options &options) |
| : options_(options), |
| bytes_downloaded_(0), |
| bytes_uploaded_(0) { |
| assert(!options_.hosts.empty()); |
| env_ = std::make_shared<http::CurlEnv>(); |
| std::random_device rd; |
| std::default_random_engine random_engine(rd()); |
| std::uniform_int_distribution<char> uniform_dist(1, 255); |
| char *data = new char[options_.upload_size]; |
| for (int i = 0; i < options_.upload_size; ++i) { |
| data[i] = uniform_dist(random_engine); |
| } |
| send_data_ = data; |
| } |
| |
| Speedtest::~Speedtest() { |
| delete[] send_data_; |
| } |
| |
| void Speedtest::Run() { |
| if (!RunPingTest()) { |
| std::cout << "No servers responded. Exiting\n"; |
| return; |
| } |
| RunDownloadTest(); |
| RunUploadTest(); |
| } |
| |
| void Speedtest::RunDownloadTest() { |
| end_download_ = false; |
| long start_time = SystemTimeMicros(); |
| bytes_downloaded_ = 0; |
| std::thread threads[options_.number]; |
| for (int i = 0; i < options_.number; ++i) { |
| threads[i] = std::thread([=]() { |
| RunDownload(i); |
| }); |
| } |
| std::thread timer([&]{ |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options_.time_millis)); |
| end_download_ = true; |
| }); |
| timer.join(); |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| long end_time = speedtest::SystemTimeMicros(); |
| |
| double running_time = (end_time - start_time) / 1000000.0; |
| double megabits = bytes_downloaded_ * 8 / 1000000.0 / running_time; |
| std::cout << "Downloaded " << bytes_downloaded_ |
| << " bytes in " << running_time * 1000 << " ms (" |
| << megabits << " Mbps)\n"; |
| } |
| |
| void Speedtest::RunDownload(int id) { |
| auto download = MakeRequest(id, "/download"); |
| http::Request::DownloadFn noop = [](void *, size_t) {}; |
| while (!end_download_) { |
| long downloaded = 0; |
| download->set_param("i", speedtest::to_string(id)); |
| download->set_param("size", speedtest::to_string(options_.download_size)); |
| download->set_param("time", speedtest::to_string(SystemTimeMicros())); |
| download->set_progress_fn([&](curl_off_t, |
| curl_off_t dlnow, |
| curl_off_t, |
| curl_off_t) -> bool { |
| if (dlnow > downloaded) { |
| bytes_downloaded_ += dlnow - downloaded; |
| downloaded = dlnow; |
| } |
| return end_download_; |
| }); |
| download->Get(noop); |
| download->Reset(); |
| } |
| } |
| |
| void Speedtest::RunUploadTest() { |
| end_upload_ = false; |
| long start_time = SystemTimeMicros(); |
| bytes_uploaded_ = 0; |
| std::thread threads[options_.number]; |
| for (int i = 0; i < options_.number; ++i) { |
| threads[i] = std::thread([=]() { |
| RunUpload(i); |
| }); |
| } |
| std::thread timer([&]{ |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options_.time_millis)); |
| end_upload_ = true; |
| }); |
| timer.join(); |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| long end_time = speedtest::SystemTimeMicros(); |
| |
| double running_time = (end_time - start_time) / 1000000.0; |
| double megabits = bytes_uploaded_ * 8 / 1000000.0 / running_time; |
| std::cout << "Uploaded " << bytes_uploaded_ |
| << " bytes in " << running_time * 1000 << " ms (" |
| << megabits << " Mbps)\n"; |
| } |
| |
| void Speedtest::RunUpload(int id) { |
| auto upload = MakeRequest(id, "/upload"); |
| while (!end_upload_) { |
| long uploaded = 0; |
| upload->set_progress_fn([&](curl_off_t, |
| curl_off_t, |
| curl_off_t, |
| curl_off_t ulnow) -> bool { |
| if (ulnow > uploaded) { |
| bytes_uploaded_ += ulnow - uploaded; |
| uploaded = ulnow; |
| } |
| return end_upload_; |
| }); |
| |
| // disable the Expect header as the server isn't expecting it (perhaps |
| // it should?). If the server isn't then libcurl waits for 1 second |
| // before sending the data anyway. So sending this header eliminated |
| // the 1 second delay. |
| upload->set_header("Expect", ""); |
| upload->Post(send_data_, options_.upload_size); |
| upload->Reset(); |
| } |
| } |
| |
| bool Speedtest::RunPingTest() { |
| end_ping_ = false; |
| size_t num_hosts = options_.hosts.size(); |
| std::thread threads[num_hosts]; |
| min_ping_micros_.clear(); |
| min_ping_micros_.resize(num_hosts); |
| for (size_t i = 0; i < num_hosts; ++i) { |
| threads[i] = std::thread([=]() { |
| RunPing(i); |
| }); |
| } |
| std::thread timer([&]{ |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options_.time_millis)); |
| end_ping_ = true; |
| }); |
| timer.join(); |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| if (options_.verbose) { |
| std::cout << "Pinged " << num_hosts << " " |
| << (num_hosts == 1 ? "host" : "hosts:") << "\n"; |
| } |
| size_t min_index = 0; |
| for (size_t i = 0; i < num_hosts; ++i) { |
| if (options_.verbose) { |
| std::cout << " " << options_.hosts[i].url() << ": "; |
| if (min_ping_micros_[i] == std::numeric_limits<long>::max()) { |
| std::cout << "no packets received"; |
| } else { |
| double ping_ms = min_ping_micros_[i] / 1000.0; |
| if (ping_ms < 10) { |
| std::cout << std::fixed << std::setprecision(1); |
| } else { |
| std::cout << std::fixed << std::setprecision(0); |
| } |
| std::cout << ping_ms << " ms"; |
| } |
| std::cout << "\n"; |
| } |
| if (min_ping_micros_[i] < min_ping_micros_[min_index]) { |
| min_index = i; |
| } |
| } |
| if (min_ping_micros_[min_index] == std::numeric_limits<long>::max()) { |
| // no servers respondeded |
| return false; |
| } |
| url_ = options_.hosts[min_index]; |
| std::cout << "Host for Speedtest: " << url_.url() << " ("; |
| double ping_ms = min_ping_micros_[min_index] / 1000.0; |
| if (ping_ms < 10) { |
| std::cout << std::fixed << std::setprecision(1); |
| } else { |
| std::cout << std::fixed << std::setprecision(0); |
| } |
| std::cout << ping_ms << " ms)\n"; |
| return true; |
| } |
| |
| void Speedtest::RunPing(size_t index) { |
| http::Request::DownloadFn noop = [](void *, size_t) {}; |
| min_ping_micros_[index] = std::numeric_limits<long>::max(); |
| http::Url url(options_.hosts[index]); |
| url.set_path("/ping"); |
| auto ping = env_->NewRequest(); |
| ping->set_url(url); |
| while (!end_ping_) { |
| long req_start = SystemTimeMicros(); |
| if (ping->Get(noop) == CURLE_OK) { |
| long req_end = SystemTimeMicros(); |
| long ping_time = req_end - req_start; |
| min_ping_micros_[index] = std::min(min_ping_micros_[index], ping_time); |
| } |
| ping->Reset(); |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| } |
| } |
| |
| std::unique_ptr<http::Request> Speedtest::MakeRequest(int id, |
| const std::string &path) { |
| auto request = env_->NewRequest(); |
| http::Url url(url_); |
| int port = (id % 20) + url.port() + 1; |
| url.set_port(port); |
| url.set_path(path); |
| request->set_url(url); |
| return std::move(request); |
| } |
| |
| } // namespace speedtest |