| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| #![deny(warnings)] |
| |
| // Explicitly added due to conflict using custom_attribute and async_await above. |
| #[macro_use] |
| extern crate serde_derive; |
| |
| mod opts; |
| |
| use { |
| failure::{Error, ResultExt, bail}, |
| fidl_fuchsia_net_oldhttp::{self as http, HttpServiceProxy}, |
| fuchsia_app::client::connect_to_service, |
| fuchsia_async as fasync, |
| fuchsia_syslog::{self as syslog, fx_log_info}, |
| fuchsia_zircon as zx, |
| futures::io::{AllowStdIo, AsyncReadExt}, |
| std::process, |
| structopt::StructOpt, |
| crate::opts::Opt, |
| }; |
| |
| fn main() -> Result<(), Error> { |
| syslog::init_with_tags(&["network-speed-test"]).expect("should not fail"); |
| |
| let opt = Opt::from_args(); |
| fx_log_info!("{:?}", opt); |
| |
| // create objects to hold test objects and results |
| let mut test_results = TestResults::default(); |
| |
| let mut test_pass = true; |
| if let Err(e) = run_test(opt, &mut test_results) { |
| test_pass = false; |
| test_results.error_message = e.to_string(); |
| } |
| |
| report_results(&mut test_results); |
| |
| if !test_pass { |
| process::exit(1); |
| } |
| |
| Ok(()) |
| } |
| |
| fn run_test(opt: Opt, test_results: &mut TestResults) -> Result<(), Error> { |
| let mut exec = fasync::Executor::new().context("error creating event loop")?; |
| |
| let http_svc = connect_to_service::<http::HttpServiceMarker>()?; |
| test_results.connect_to_http_service = true; |
| |
| let url_request = create_url_request(opt.target_url); |
| let ind_download = exec.run_singlethreaded( |
| fetch_and_discard_url(http_svc, url_request))?; |
| test_results.base_data_transfer = true; |
| |
| // TODO (NET-1665): aggregate info from individual results (when we do multiple requests) |
| test_results.overall_avg_goodput_mbps = ind_download.goodput_mbps; |
| test_results.total_bytes = ind_download.bytes; |
| test_results.total_nanos = ind_download.nanos; |
| |
| Ok(()) |
| } |
| |
| // Object to hold overall test status |
| #[derive(Default, Serialize)] |
| struct TestResults { |
| connect_to_http_service: bool, |
| base_data_transfer: bool, |
| overall_avg_goodput_mbps: f64, |
| total_bytes: u64, |
| total_nanos: u64, |
| |
| error_message: String |
| } |
| |
| // Object to hold results of a single download |
| #[derive(Default, Serialize)] |
| struct IndividualDownload { |
| bytes: u64, |
| nanos: u64, |
| goodput_mbps: f64 |
| } |
| |
| fn report_results(test_results: &TestResults) { |
| println!("{}", serde_json::to_string_pretty(&test_results).unwrap()); |
| } |
| |
| fn create_url_request<T: Into<String>>(url_string: T) -> http::UrlRequest { |
| http::UrlRequest { |
| url: url_string.into(), |
| method: String::from("GET"), |
| headers: None, |
| body: None, |
| response_body_buffer_size: 0, |
| auto_follow_redirects: true, |
| cache_mode: http::CacheMode::BypassCache, |
| response_body_mode: http::ResponseBodyMode::Stream, |
| } |
| } |
| |
| // TODO (NET-1663): move to helper method |
| // TODO (NET-1664): verify checksum on data received |
| async fn fetch_and_discard_url(http_service: HttpServiceProxy, |
| mut url_request: http::UrlRequest) |
| -> Result<IndividualDownload, failure::Error> { |
| |
| // Create a UrlLoader instance |
| let (s, p) = zx::Channel::create().context("failed to create zx channel")?; |
| let proxy = fasync::Channel::from_channel(p).context("failed to make async channel")?; |
| |
| let loader_server = fidl::endpoints::ServerEnd::<http::UrlLoaderMarker>::new(s); |
| http_service.create_url_loader(loader_server)?; |
| |
| let loader_proxy = http::UrlLoaderProxy::new(proxy); |
| let start_time = zx::Time::get(zx::ClockId::Monotonic); |
| let response = await!(loader_proxy.start(&mut url_request))?; |
| |
| if let Some(e) = response.error { |
| bail!("UrlLoaderProxy error - code:{} ({})", e.code, e.description.unwrap_or("".into())) |
| } |
| |
| let mut socket = match response.body.map(|x| *x) { |
| Some(http::UrlBody::Stream(s)) => fasync::Socket::from_socket(s)?, |
| _ => { |
| bail!("failed to read UrlBody from the stream - error: {}", zx::Status::BAD_STATE); |
| } |
| }; |
| |
| // discard the bytes |
| let mut stdio_sink = AllowStdIo::new(::std::io::sink()); |
| let bytes_received = await!(socket.copy_into(&mut stdio_sink))?; |
| let stop_time = zx::Time::get(zx::ClockId::Monotonic); |
| |
| let time_nanos = (stop_time - start_time).nanos() as u64; |
| let time_seconds = time_nanos as f64 * 1e-9; |
| |
| let bits_received = (bytes_received * 8) as f64; |
| |
| fx_log_info!("Received {} bytes in {:.3} seconds", bytes_received, time_seconds); |
| |
| if bytes_received < 1 { |
| bail!("Failed to download data from url! bytes_received = {}", bytes_received); |
| } |
| |
| let megabits_per_sec = bits_received * 1e-6 / time_seconds; |
| |
| let mut individual_download = IndividualDownload::default(); |
| individual_download.goodput_mbps = megabits_per_sec; |
| individual_download.bytes = bytes_received; |
| individual_download.nanos = time_nanos; |
| |
| Ok(individual_download) |
| } |
| |