| /* |
| * 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. |
| */ |
| |
| #ifndef SPEEDTEST_TRANSFER_RUNNER_H |
| #define SPEEDTEST_TRANSFER_RUNNER_H |
| |
| #include <functional> |
| #include <iostream> |
| #include <memory> |
| #include <mutex> |
| #include <thread> |
| #include <vector> |
| #include "status.h" |
| #include "utils.h" |
| |
| namespace speedtest { |
| |
| struct Bucket { |
| long total_bytes = 0; |
| long start_time = 0; |
| double short_megabits = 0.0; |
| double long_megabits = 0.0; |
| }; |
| |
| struct TransferOptions { |
| bool verbose = false; |
| long min_runtime_millis = 0; |
| long max_runtime_millis = 0; |
| long interval_millis = 0; |
| long progress_millis = 0; |
| int min_intervals = 0; |
| int max_intervals = 0; |
| double max_variance = 0.0; |
| bool exponential_moving_average = false; |
| std::function<void(const Bucket)> progress_fn; |
| }; |
| |
| struct TransferResult { |
| long start_time; |
| long end_time; |
| Status status; |
| std::vector<Bucket> buckets; |
| double speed_mbps; |
| long total_bytes; |
| }; |
| |
| double GetShortEma(std::vector<Bucket> *buckets, int num_buckets); |
| double GetLongEma(std::vector<Bucket> *buckets, int num_intervals); |
| double GetSimpleAverage(std::vector<Bucket> *buckets, int num_intervals); |
| |
| // Run a variable length transfer test using two moving averages. |
| // The test runs between min_runtime and max_runtime and otherwise |
| // ends when the speed is "stable" meaning the two moving averages |
| // are relatively close to one another. |
| template <typename F> |
| TransferResult |
| RunTransfer(F &&fn, std::atomic_bool *cancel, TransferOptions options) { |
| TransferResult result; |
| result.start_time = SystemTimeMicros(); |
| |
| // sentinel value of all zeroes |
| result.buckets.emplace_back(); |
| |
| // If progress updates are created add a thread to send updates |
| std::thread progress; |
| std::atomic_bool local_cancel(false); |
| if (options.progress_fn && options.progress_millis > 0) { |
| if (options.verbose) { |
| std::cout << "Progress updates every " |
| << options.progress_millis << " ms\n"; |
| } |
| progress = std::thread([&] { |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options.progress_millis)); |
| while (!local_cancel) { |
| options.progress_fn(result.buckets.back()); |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options.progress_millis)); |
| } |
| options.progress_fn(result.buckets.back()); |
| }); |
| } else if (options.verbose) { |
| std::cout << "No progress updates\n"; |
| } |
| |
| // Updating thread |
| if (options.verbose) { |
| std::cout << "Transfer runner updates every " |
| << options.interval_millis << " ms\n"; |
| } |
| long min_runtime_micros = options.min_runtime_millis * 1000; |
| long max_runtime_micros = options.max_runtime_millis * 1000; |
| std::mutex mutex; |
| std::thread updater([&] { |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options.interval_millis)); |
| while (!local_cancel) { |
| if (*cancel) { |
| local_cancel = true; |
| break; |
| } |
| |
| Bucket last_bucket; |
| long running_time = SystemTimeMicros() - result.start_time; |
| { |
| std::lock_guard <std::mutex> lock(mutex); |
| result.buckets.emplace_back(); |
| Bucket &bucket = result.buckets.back(); |
| bucket.start_time = running_time; |
| bucket.total_bytes = fn.get().bytes_transferred(); |
| result.total_bytes = bucket.total_bytes; |
| if (options.exponential_moving_average) { |
| bucket.short_megabits = GetShortEma(&result.buckets, |
| options.min_intervals); |
| bucket.long_megabits = GetLongEma(&result.buckets, |
| options.max_intervals); |
| } else { |
| bucket.short_megabits = GetSimpleAverage(&result.buckets, |
| options.min_intervals); |
| bucket.long_megabits = GetSimpleAverage(&result.buckets, |
| options.max_intervals); |
| } |
| result.speed_mbps = bucket.long_megabits; |
| last_bucket = result.buckets.back(); |
| } |
| |
| if (running_time > max_runtime_micros) { |
| local_cancel = true; |
| break; |
| } |
| if (running_time > min_runtime_micros && |
| last_bucket.short_megabits > 0 && |
| last_bucket.long_megabits > 0) { |
| double speed_variance = variance(last_bucket.short_megabits, |
| last_bucket.long_megabits); |
| if (speed_variance <= options.max_variance) { |
| local_cancel = true; |
| break; |
| } |
| } |
| std::this_thread::sleep_for( |
| std::chrono::milliseconds(options.interval_millis)); |
| } |
| }); |
| |
| // transfer task |
| std::thread task([&]{ |
| fn(&local_cancel); |
| }); |
| |
| task.join(); |
| updater.join(); |
| if (progress.joinable()) { |
| progress.join(); |
| } |
| |
| if (*cancel) { |
| result.status = Status(StatusCode::ABORTED, "transfer runner aborted"); |
| } else { |
| result.status = Status::OK; |
| } |
| result.end_time = SystemTimeMicros(); |
| return result; |
| } |
| |
| } // namespace |
| |
| #endif // SPEEDTEST_TRANSFER_RUNNER_H |