blob: 4d070997edcdc3576ee66f3615198bda77e53ffb [file] [log] [blame]
/*
* 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 "options.h"
#include <assert.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "url.h"
namespace speedtest {
const char* kDefaultHost = "speedtest.googlefiber.net";
namespace {
bool ParseLong(const char *s, char **endptr, long *size) {
assert(s != nullptr);
assert(size != nullptr);
*size = strtol(s, endptr, 10);
return !**endptr;
}
bool ParseSize(const char *s, long *size) {
long result;
char *endptr;
bool success = ParseLong(s, &endptr, &result);
if (result <= 0) {
return false;
}
if (success) {
*size = result;
return true;
}
if (strcasecmp(endptr, "m") == 0) {
result <<= 20;
} else if (strcasecmp(endptr, "k") == 0) {
result <<= 10;
} else {
return false;
}
*size = result;
return true;
}
const char *kShortOpts = "d:u:t:n:p:s:vh";
struct option kLongOpts[] = {
{"help", no_argument, nullptr, 'h'},
{"number", required_argument, nullptr, 'n'},
{"download_size", required_argument, nullptr, 'd'},
{"upload_size", required_argument, nullptr, 'u'},
{"progress", required_argument, nullptr, 'p'},
{"serverid", required_argument, nullptr, 's'}, // ignored
{"time", required_argument, nullptr, 't'},
{"verbose", no_argument, nullptr, 'v'},
{nullptr, 0, nullptr, 0},
};
const int kDefaultNumber = 10;
const int kDefaultDownloadSize = 10000000;
const int kDefaultUploadSize = 10000000;
const int kDefaultTimeMillis = 5000;
const int kMaxNumber = 1000;
const int kMaxProgress = 1000000;
} // namespace
bool ParseOptions(int argc, char *argv[], Options *options) {
assert(options != nullptr);
options->number = kDefaultNumber;
options->download_size = kDefaultDownloadSize;
options->upload_size = kDefaultUploadSize;
options->time_millis = kDefaultTimeMillis;
options->progress_millis = 0;
options->verbose = false;
options->usage = false;
options->hosts.clear();
// Manually set this to 0 to allow repeated calls
optind = 0;
int opt = 0, long_index = 0;
while ((opt = getopt_long(argc, argv,
kShortOpts, kLongOpts, &long_index)) != -1) {
switch (opt) {
case 'd':
if (!ParseSize(optarg, &options->download_size)) {
std::cerr << "Invalid download size '" << optarg << "'\n";
return false;
}
break;
case 'u':
if (!ParseSize(optarg, &options->upload_size)) {
std::cerr << "Invalid upload size '" << optarg << "'\n";
return false;
}
break;
case 't':
options->time_millis = atoi(optarg);
break;
case 'v':
options->verbose = true;
break;
case 'h':
options->usage = true;
return true;
case 'n': {
long number;
char *endptr;
if (!ParseLong(optarg, &endptr, &number)) {
std::cerr << "Could not parse number '" << optarg << "'\n";
return false;
}
if (number < 1 || number > kMaxNumber) {
std::cerr << "Number must be between 1 and " << kMaxNumber
<< ", got '" << optarg << "'\n";
return false;
}
options->number = static_cast<int>(number);
break;
}
case 'p': {
long progress;
char *endptr;
if (!ParseLong(optarg, &endptr, &progress)) {
std::cerr << "Could not parse interval '" << optarg << "'\n";
return false;
}
if (progress < 0 || progress > kMaxProgress) {
std::cerr << "Number must be between 0 and " << kMaxProgress
<< ", got '" << optarg << "'\n";
return false;
}
options->progress_millis = static_cast<int>(progress);
break;
}
case 's':
// serverid is an argument supported by the older speedtest
// implementation. It is ignored here to ease the transition.
break;
default:
return false;
}
}
if (optind < argc) {
for (int i = optind; i < argc; i++) {
http::Url url;
if (!url.Parse(argv[i])) {
std::cerr << "Could not parse URL '" << argv[i] << "'\n";
return false;
}
if (!url.absolute()) {
std::cerr << "URL '" << argv[i] << "' is not absolute\n";
return false;
}
url.clear_path();
url.clear_query_string();
url.clear_fragment();
options->hosts.emplace_back(url);
}
}
if (options->hosts.empty()) {
options->hosts.emplace_back(http::Url(kDefaultHost));
}
return true;
}
void PrintOptions(const Options &options) {
PrintOptions(std::cout, options);
}
void PrintOptions(std::ostream &out, const Options &options) {
out << "Number: " << options.number << "\n"
<< "Upload size: " << options.upload_size << "\n"
<< "Download size: " << options.download_size << "\n"
<< "Time: " << options.time_millis << " ms\n"
<< "Progress interval: " << options.progress_millis << " ms\n"
<< "Verbose: " << (options.verbose ? "true" : "false") << "\n"
<< "Usage: " << (options.usage ? "true" : "false") << "\n"
<< "Hosts:\n";
for (const http::Url &host : options.hosts) {
out << " " << host.url() << "\n";
}
}
void PrintUsage(const char *app_name) {
PrintUsage(std::cout, app_name);
}
void PrintUsage(std::ostream &out, const char *app_path) {
assert(app_path != nullptr);
const char *last_slash = strrchr(app_path, '/');
const char *app_name = last_slash == nullptr ? app_path : last_slash + 1;
out << basename(app_name) << ": run an HTTP speedtest\n\n"
<< "Usage: speedtest [options] <host> ...\n"
<< " -h, --help This help text\n"
<< " -n, --number NUM Number of simultaneous transfers\n"
<< " -d, --download_size SIZE Download size in bytes\n"
<< " -u, --upload_size SIZE Upload size in bytes\n"
<< " -t, --time TIME Time per test in milliseconds\n"
<< " -p, --progress TIME Progress intervals in milliseconds\n"
<< " -v, --verbose Verbose output\n";
}
} // namespace speedtest