blob: 93a024857dde1c160bbbf63f36b1c18fb8639378 [file] [log] [blame]
/*
* Copyright 2016 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 <curl/curl.h>
#include <jsoncpp/json/json.h>
#include <jsoncpp/json/writer.h>
#include "download.h"
#include "errors.h"
#include "result.h"
#include "timed_runner.h"
#include "upload.h"
namespace speedtest {
Speedtest::Speedtest(const Options &options): options_(options) {
}
Speedtest::Result Speedtest::operator()(std::atomic_bool *cancel) {
Speedtest::Result result;
result.start_time = SystemTimeMicros();
result.download_run = false;
result.upload_run = false;
result.ping_run = false;
if (*cancel) {
result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
result.end_time = SystemTimeMicros();
return result;
}
Init::Options init_options;
init_options.verbose = options_.verbose;
init_options.request_factory = options_.request_factory;
init_options.global = options_.global;
init_options.global_url = options_.global_url;
init_options.ping_timeout_millis = options_.ping_timeout_millis;
init_options.regional_urls = options_.regional_urls;
Init init(init_options);
result.init_result = init(cancel);
if (!result.init_result.status.ok()) {
result.status = result.init_result.status;
result.end_time = SystemTimeMicros();
return result;
}
selected_region_ = result.init_result.selected_region;
if (options_.verbose) {
std::cout << "Setting selected region to "
<< DescribeRegion(selected_region_) << "\n";
}
if (result.init_result.config_result.config.location_id.empty()) {
result.init_result.config_result.config.location_id = selected_region_.id;
}
if (result.init_result.config_result.config.location_name.empty()) {
result.init_result.config_result.config.location_name = selected_region_.name;
}
OverrideConfigWithOptions(&result.init_result.config_result.config, options_);
config_ = result.init_result.config_result.config;
if (options_.verbose) {
PrintConfig(config_);
}
if (*cancel) {
result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
result.end_time = SystemTimeMicros();
return result;
}
std::cout << "ID: " << result.init_result.selected_region.id << "\n";
std::cout << "Location: " << result.init_result.selected_region.name << "\n";
if (options_.skip_download) {
std::cout << "Skipping download test\n";
} else {
result.download_result = RunDownloadTest(cancel);
if (!result.download_result.status.ok()) {
result.status = result.download_result.status;
result.end_time = SystemTimeMicros();
return result;
}
result.download_run = true;
std::cout << "Download speed: "
<< round(result.download_result.speed_mbps, 2)
<< " Mbps\n";
}
if (*cancel) {
result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
result.end_time = SystemTimeMicros();
return result;
}
if (options_.skip_upload) {
std::cout << "Skipping upload test\n";
} else {
result.upload_result = RunUploadTest(cancel);
if (!result.upload_result.status.ok()) {
result.status = result.upload_result.status;
result.end_time = SystemTimeMicros();
return result;
}
result.upload_run = true;
std::cout << "Upload speed: "
<< round(result.upload_result.speed_mbps, 2)
<< " Mbps\n";
}
if (*cancel) {
result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
result.end_time = SystemTimeMicros();
return result;
}
if (options_.skip_ping) {
std::cout << "Skipping ping test\n";
} else {
result.ping_result = RunPingTest(cancel);
if (!result.ping_result.status.ok()) {
result.status = result.ping_result.status;
result.end_time = SystemTimeMicros();
return result;
}
result.ping_run = true;
std::cout << "Ping time: "
<< ToMillis(result.ping_result.min_ping_micros)
<< " ms\n";
}
result.status = Status::OK;
result.end_time = SystemTimeMicros();
if (!options_.report_results) {
if (options_.verbose) {
std::cout << "Not reporting results to server\n";
}
} else {
Json::Value root;
PopulateSpeedtest(root, result);
Json::FastWriter writer;
std::string out = writer.write(root);
http::Url result_url(selected_region_.urls.front());
result_url.set_path("/result");
if (options_.verbose) {
std::cout << "Posting results to " << result_url.url() << "\n";
}
http::Request::Ptr request = options_.request_factory(result_url);
request->set_header("Content-Type", "application/json");
CURLcode curl_code = request->Post(out.c_str(), out.size());
if (curl_code == CURLE_OK) {
if (options_.verbose) {
std::cout << "Result posted successfully\n";
}
} else {
std::cout << "Failed to report results: "
<< http::ErrorString(curl_code) << "\n";
}
}
return result;
}
TransferResult Speedtest::RunDownloadTest(std::atomic_bool *cancel) {
if (options_.verbose) {
std::cout << "Starting download test to "
<< DescribeRegion(selected_region_) << ")\n";
}
Download::Options download_options;
download_options.verbose = options_.verbose;
download_options.num_transfers = config_.num_downloads;
download_options.download_bytes = config_.download_bytes;
download_options.request_factory = [this](int id) -> http::Request::Ptr{
return MakeTransferRequest(id, "/download");
};
Download download(download_options);
TransferOptions transfer_options;
transfer_options.verbose = options_.verbose;
transfer_options.min_runtime_millis = config_.min_transfer_runtime;
transfer_options.max_runtime_millis = config_.max_transfer_runtime;
transfer_options.min_intervals = config_.min_transfer_intervals;
transfer_options.max_intervals = config_.max_transfer_intervals;
transfer_options.max_variance = config_.max_transfer_variance;
transfer_options.interval_millis = config_.interval_millis;
transfer_options.exponential_moving_average =
config_.average_type == "EXPONENTIAL";
if (options_.progress_millis > 0) {
transfer_options.progress_millis = options_.progress_millis;
transfer_options.progress_fn = [](Bucket bucket) {
double speed_variance = variance(bucket.short_megabits,
bucket.long_megabits);
std::cout << "[+" << round(bucket.start_time / 1000.0, 0) << " ms] "
<< "Download speed: " << round(bucket.short_megabits, 2)
<< " - " << round(bucket.long_megabits, 2)
<< " Mbps (" << bucket.total_bytes << " bytes, variance "
<< round(speed_variance, 4) << ")\n";
};
}
return RunTransfer(std::ref(download), cancel, transfer_options);
}
TransferResult Speedtest::RunUploadTest(std::atomic_bool *cancel) {
if (options_.verbose) {
std::cout << "Starting upload test to "
<< DescribeRegion(selected_region_) << ")\n";
}
Upload::Options upload_options;
upload_options.verbose = options_.verbose;
upload_options.num_transfers = config_.num_uploads;
upload_options.payload = MakeRandomData(config_.upload_bytes);
upload_options.request_factory = [this](int id) -> http::Request::Ptr{
return MakeTransferRequest(id, "/upload");
};
Upload upload(upload_options);
TransferOptions transfer_options;
transfer_options.verbose = options_.verbose;
transfer_options.min_runtime_millis = config_.min_transfer_runtime;
transfer_options.max_runtime_millis = config_.max_transfer_runtime;
transfer_options.min_intervals = config_.min_transfer_intervals;
transfer_options.max_intervals = config_.max_transfer_intervals;
transfer_options.max_variance = config_.max_transfer_variance;
transfer_options.interval_millis = config_.interval_millis;
transfer_options.exponential_moving_average =
config_.average_type == "EXPONENTIAL";
if (options_.progress_millis > 0) {
transfer_options.progress_millis = options_.progress_millis;
transfer_options.progress_fn = [](Bucket bucket) {
double speed_variance = variance(bucket.short_megabits,
bucket.long_megabits);
std::cout << "[+" << round(bucket.start_time / 1000.0, 0) << " ms] "
<< "Upload speed: " << round(bucket.short_megabits, 2)
<< " - " << round(bucket.long_megabits, 2)
<< " Mbps (" << bucket.total_bytes << " bytes, variance "
<< round(speed_variance, 4) << ")\n";
};
}
return RunTransfer(std::ref(upload), cancel, transfer_options);
}
Ping::Result Speedtest::RunPingTest(std::atomic_bool *cancel) {
Ping::Options ping_options;
ping_options.verbose = options_.verbose;
ping_options.timeout_millis = config_.ping_timeout_millis;
ping_options.region = selected_region_;
ping_options.num_concurrent_pings = 0;
ping_options.request_factory = [&](const http::Url &url){
return MakeRequest(url);
};
Ping ping(ping_options);
return RunTimed(std::ref(ping), cancel, config_.ping_runtime_millis);
}
void Speedtest::OverrideConfigWithOptions(Config *config,
const Options &options) {
if (options_.num_downloads > 0) {
config->num_downloads = options_.num_downloads;
}
if (options_.download_bytes > 0) {
config->download_bytes = options_.download_bytes;
}
if (options_.num_uploads > 0) {
config->num_uploads = options_.num_uploads;
}
if (options_.upload_bytes > 0) {
config->upload_bytes = options_.upload_bytes;
}
if (options_.ping_runtime_millis > 0) {
config->ping_runtime_millis = options_.ping_runtime_millis;
}
if (options_.ping_timeout_millis > 0) {
config->ping_timeout_millis = options_.ping_timeout_millis;
}
if (options_.min_transfer_runtime > 0) {
config->min_transfer_runtime = options_.min_transfer_runtime;
}
if (options_.max_transfer_runtime > 0) {
config->max_transfer_runtime = options_.max_transfer_runtime;
}
if (options_.min_transfer_intervals > 0) {
config->min_transfer_intervals = options_.min_transfer_intervals;
}
if (options_.max_transfer_intervals > 0) {
config->max_transfer_intervals = options_.max_transfer_intervals;
}
if (options_.max_transfer_variance > 0) {
config->max_transfer_variance = options_.max_transfer_variance;
}
if (options_.interval_millis > 0) {
config->interval_millis = options_.interval_millis;
}
if (options_.exponential_moving_average){
config->average_type = "EXPONENTIAL";
}
}
http::Request::Ptr Speedtest::MakeRequest(const http::Url &url) const {
http::Request::Ptr request = options_.request_factory(url);
if (!options_.user_agent.empty()) {
request->set_user_agent(options_.user_agent);
}
return std::move(request);
}
http::Request::Ptr Speedtest::MakeBaseRequest(
int id, const std::string &path) const {
http::Url url(selected_region_.urls.front());
url.set_path(path);
return MakeRequest(url);
}
http::Request::Ptr Speedtest::MakeTransferRequest(
int id, const std::string &path) const {
http::Url url(selected_region_.urls.front().url());
int port_start = config_.transfer_port_start;
int port_end = config_.transfer_port_end;
int num_ports = port_end - port_start + 1;
if (num_ports > 0) {
url.set_port(port_start + (id % num_ports));
}
url.set_path(path);
return MakeRequest(url);
}
} // namespace speedtest