diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..1c60b19 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,10 @@ +^.*\.Rproj$ +^\.Rproj\.user$ +^\.travis\.yml$ +^README\.*Rmd$ +^README\.*html$ +^NOTES\.*Rmd$ +^NOTES\.*html$ +^\.codecov\.yml$ +^README_files$ +^doc$ diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..69cb760 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cce1f17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.Rproj.user +.Rhistory +.RData +.Rproj +src/*.o +src/*.so +src/*.dll diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..76d9586 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: r + +warnings_are_errors: true + +sudo: required + +cache: packages + +r: + - oldrel + - release + - devel + +apt_packages: + - libv8-dev + - xclip + +env: + global: + - CRAN: http://cran.rstudio.com + +after_success: + - Rscript -e 'covr::codecov()' + +notifications: + email: + - bob@rud.is + irc: + channels: + - "104.236.112.222#builds" + nick: travisci diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..ade3d83 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,30 @@ +Package: speedtest +Type: Package +Title: speedtest title goes here otherwise CRAN checks fail +Version: 0.1.0 +Date: 2017-11-09 +Authors@R: c( + person("Bob", "Rudis", email = "bob@rud.is", role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-5670-2640")) + ) +Author: Bob Rudis (bob@rud.is) +Maintainer: Bob Rudis +Description: A good description goes here otherwise CRAN checks fail. +URL: https://github.com/hrbrmstr/speedtest +BugReports: https://github.com/hrbrmstr/speedtest/issues +License: AGPL +Suggests: + testthat, + covr +Depends: + R (>= 3.2.0) +Imports: + curl, + purrr, + dplyr, + xml2, + utils, + pingr, + urltools, + jsonlite +RoxygenNote: 6.0.1 diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..890f08d --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,20 @@ +# Generated by roxygen2: do not edit by hand + +export(spd_best_servers) +export(spd_closest_servers) +export(spd_config) +export(spd_download_test) +export(spd_servers) +import(httr) +import(purrr) +import(xml2) +importFrom(curl,curl_fetch_multi) +importFrom(curl,multi_run) +importFrom(dplyr,arrange) +importFrom(dplyr,data_frame) +importFrom(dplyr,filter) +importFrom(dplyr,left_join) +importFrom(jsonlite,fromJSON) +importFrom(pingr,ping) +importFrom(urltools,domain) +importFrom(utils,globalVariables) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..9b4679b --- /dev/null +++ b/NEWS.md @@ -0,0 +1,2 @@ +0.1.0 +* Initial release diff --git a/R/aaa.r b/R/aaa.r new file mode 100644 index 0000000..1c7369e --- /dev/null +++ b/R/aaa.r @@ -0,0 +1,3 @@ +utils::globalVariables( + c("total", "latency_url", "test_result", "ping_time", "total_time", "retrieval_time", + "bw", "size", "secs")) \ No newline at end of file diff --git a/R/bestest.r b/R/bestest.r new file mode 100644 index 0000000..a8cb6e2 --- /dev/null +++ b/R/bestest.r @@ -0,0 +1,52 @@ +#' Find "best" servers (latency-wise) from master server list +#' +#' @md +#' @param servers if not `NULL`, then the data frame from [spd_servers()]. If +#' `NULL`, then [spd_servers()] will be called to retrieve the server list. +#' @param config client configuration retrieved via [spd_config()]. If `NULL` it +#' will be retrieved +#' @return server list in order of latency closeness (retrieval speed column included) +#' @note the list of target servers will be truncated to the first 10 +#' @export +spd_best_servers <- function(servers=NULL, config=NULL) { + + if (is.null(config)) config <- spd_config() + if (is.null(servers)) servers <- spd_closest_servers(config=config) + + targets <- servers + + if (nrow(targets) > 10) targets <- servers[1:10,] + + .lat_dat <- list() + + .COK <- function(res) { + .lat_dat <<- c(.lat_dat, list(res)) + } + + .CERR <- function(res) { cat("X")} + + targets$latency_url <- file.path(dirname(targets$url), "latency.txt") + purrr::walk(targets$latency_url, curl::curl_fetch_multi, .COK, .CERR) + + curl::multi_run() + + purrr::map_df(.lat_dat, ~{ + data_frame( + latency_url = .x$url, + ping_time = mean(pingr::ping(urltools::domain(.x$url)), na.rm=TRUE)/1000, + total_time = .x$times["total"], + retrieval_time = .x$times[6] - .x$times[5], + test_result = rawToChar(.x$content) + ) + }) %>% + dplyr::filter(!grepl("test=test", retrieval_time)) -> target_df + + # order() is kinda not necessary since the first ones to finish are going to be + # in the list first, but it's best to be safe + + dplyr::left_join(target_df, targets, "latency_url") %>% + dplyr::arrange(retrieval_time) %>% + dplyr::select(-latency_url, -test_result) + +} + diff --git a/R/closest.r b/R/closest.r new file mode 100644 index 0000000..8d135e4 --- /dev/null +++ b/R/closest.r @@ -0,0 +1,25 @@ +#' #' Find "closest" servers (geography-wise) from master server list +#' +#' Uses [ipinfo.io](https://ipinfo.io) to geolocate your external IP address. +#' +#' @md +#' @param servers if not `NULL`, then the data frame from [spd_servers()]. If +#' `NULL`, then [spd_servers()] will be called to retrieve the server list. +#' @param config client configuration retrieved via [spd_config()]. If `NULL` it +#' will be retrieved +#' @return server list in order of geographic closeness +#' @export +spd_closest_servers <- function(servers=NULL, config=NULL) { + + if (is.null(config)) config <- spd_config() + + if (is.null(servers)) servers <- spd_servers(config) + + # we don't need great circle for this, just best effort + idx <- order(sqrt((servers$lat - as.numeric(config$client$lat))^2 + + (servers$lng - as.numeric(config$client$lon))^2)) + + servers[idx,] + +} + diff --git a/R/config.r b/R/config.r new file mode 100644 index 0000000..8387f9b --- /dev/null +++ b/R/config.r @@ -0,0 +1,19 @@ +#' Retrieve client configuration information for the speedtest +#' +#' @export +spd_config <- function() { + + res <- httr::GET("http://www.speedtest.net/speedtest-config.php") + + httr::stop_for_status(res) + + config <- httr::content(res, as="text") + config <- xml2::read_xml(config) + config <- xml2::as_list(config) + config <- purrr::map(config, function(.x) { c(.x, attributes(.x)) }) + config$`server-config`$ignoreids <- strsplit(config$`server-config`$ignoreids, ",")[[1]] + + config + +} + diff --git a/R/download.r b/R/download.r new file mode 100644 index 0000000..5282a76 --- /dev/null +++ b/R/download.r @@ -0,0 +1,37 @@ +#' Download test +#' +#' @export +spd_download_test <- function(server, config=NULL) { + + if (nrow(server) > 1) server <- server[1,] + + server <- unclass(server) + + down_sizes <- c(350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000) + + dl_urls <- sprintf("%s/random%sx%s.jpg", dirname(server$url), down_sizes, down_sizes) + + pb <- dplyr::progress_estimated(length(dl_urls)) + purrr::map(dl_urls, ~{ + pb$tick()$print() + httr::GET( + url = .x, + httr::add_headers( + `Referer` = "http://c.speedtest.net/flash/speedtest.swf", + `Cache-Control` = "no-cache" + ), + httr::user_agent( + splashr::ua_macos_chrome + ), + query=list(ts=as.numeric(Sys.time())) + ) + }) -> dl_resp + + purrr::discard(dl_resp, ~.x$status_code != 200) %>% + purrr::map_df(~{ + list(secs = .x$times[6] - .x$times[5], size = (length(.x$content) + length(.x$header))) + }) %>% + dplyr::mutate(bw = ((size/secs)*8) / 1024 / 1024) %>% + dplyr::summarise(min=min(bw), mean=mean(bw), median=median(bw), max=max(bw), sd=sd(bw), var=var(bw)) + +} diff --git a/R/servers.r b/R/servers.r new file mode 100644 index 0000000..512d031 --- /dev/null +++ b/R/servers.r @@ -0,0 +1,34 @@ +#' Retrieve a list of SpeedTest servers +#' +#' @param config client configuration retrieved via [spd_config()]. If `NULL` it +#' will be retrieved +#' @return data frame +#' @export +spd_servers <- function(config=NULL) { + + res <- httr::GET("https://www.speedtest.net/speedtest-servers-static.php") + + httr::stop_for_status(res) + + if (is.null(config)) config <- spd_config() + + httr::content(res, as="text") %>% + read_xml() %>% + xml2::xml_find_all(xpath="//settings/servers/server") %>% + purrr::map_df(~{ + list( + url = xml2::xml_attr(.x, "url") %||% NA_character_, + lat = as.numeric(xml2::xml_attr(.x, "lat") %||% NA_real_), + lng = as.numeric(xml2::xml_attr(.x, "lon")) %||% NA_real_, + name = xml2::xml_attr(.x, "name") %||% NA_character_, + country = xml2::xml_attr(.x, "country") %||% NA_character_, + cc = xml2::xml_attr(.x, "cc") %||% NA_character_, + sponsor = xml2::xml_attr(.x, "sponsor") %||% NA_character_, + id = xml2::xml_attr(.x, "id") %||% NA_character_, + host = xml2::xml_attr(.x, "host") %||% NA_character_, + url2 = xml2::xml_attr(.x, "url2") %||% NA_character_ + ) + }) %>% + dplyr::filter(!(id %in% config$`server-config`$ignoreids)) + +} diff --git a/R/speedtest-package.R b/R/speedtest-package.R new file mode 100644 index 0000000..118b75c --- /dev/null +++ b/R/speedtest-package.R @@ -0,0 +1,13 @@ +#' ... +#' +#' @name speedtest +#' @docType package +#' @author Bob Rudis (bob@@rud.is) +#' @import purrr xml2 httr +#' @importFrom utils globalVariables +#' @importFrom dplyr left_join arrange filter data_frame +#' @importFrom jsonlite fromJSON +#' @importFrom curl curl_fetch_multi multi_run +#' @importFrom pingr ping +#' @importFrom urltools domain +NULL diff --git a/R/util.r b/R/util.r new file mode 100644 index 0000000..d5a5200 --- /dev/null +++ b/R/util.r @@ -0,0 +1 @@ +sGET <- purrr::safely(httr::GET) \ No newline at end of file diff --git a/README.Rmd b/README.Rmd new file mode 100644 index 0000000..8697903 --- /dev/null +++ b/README.Rmd @@ -0,0 +1,32 @@ +--- +output: rmarkdown::github_document +--- + +# speedtest + +## Description + +## What's Inside The Tin + +The following functions are implemented: + +## Installation + +```{r eval=FALSE} +devtools::install_github("hrbrmstr/speedtest") +``` + +```{r message=FALSE, warning=FALSE, error=FALSE, include=FALSE} +options(width=120) +``` + +## Usage + +```{r message=FALSE, warning=FALSE, error=FALSE} +library(speedtest) + +# current verison +packageVersion("speedtest") + +``` + diff --git a/man/spd_best_servers.Rd b/man/spd_best_servers.Rd new file mode 100644 index 0000000..03de280 --- /dev/null +++ b/man/spd_best_servers.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/bestest.r +\name{spd_best_servers} +\alias{spd_best_servers} +\title{Find "best" servers (latency-wise) from master server list} +\usage{ +spd_best_servers(servers = NULL, config = NULL) +} +\arguments{ +\item{servers}{if not \code{NULL}, then the data frame from \code{\link[=spd_servers]{spd_servers()}}. If +\code{NULL}, then \code{\link[=spd_servers]{spd_servers()}} will be called to retrieve the server list.} + +\item{config}{client configuration retrieved via \code{\link[=spd_config]{spd_config()}}. If \code{NULL} it +will be retrieved} +} +\value{ +server list in order of latency closeness (retrieval speed column included) +} +\description{ +Find "best" servers (latency-wise) from master server list +} +\note{ +the list of target servers will be truncated to the first 10 +} diff --git a/man/spd_closest_servers.Rd b/man/spd_closest_servers.Rd new file mode 100644 index 0000000..e9de124 --- /dev/null +++ b/man/spd_closest_servers.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/closest.r +\name{spd_closest_servers} +\alias{spd_closest_servers} +\title{#' Find "closest" servers (geography-wise) from master server list} +\usage{ +spd_closest_servers(servers = NULL, config = NULL) +} +\arguments{ +\item{servers}{if not \code{NULL}, then the data frame from \code{\link[=spd_servers]{spd_servers()}}. If +\code{NULL}, then \code{\link[=spd_servers]{spd_servers()}} will be called to retrieve the server list.} + +\item{config}{client configuration retrieved via \code{\link[=spd_config]{spd_config()}}. If \code{NULL} it +will be retrieved} +} +\value{ +server list in order of geographic closeness +} +\description{ +Uses \href{https://ipinfo.io}{ipinfo.io} to geolocate your external IP address. +} diff --git a/man/spd_config.Rd b/man/spd_config.Rd new file mode 100644 index 0000000..bbd1a34 --- /dev/null +++ b/man/spd_config.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.r +\name{spd_config} +\alias{spd_config} +\title{Retrieve client configuration information for the speedtest} +\usage{ +spd_config() +} +\description{ +Retrieve client configuration information for the speedtest +} diff --git a/man/spd_download_test.Rd b/man/spd_download_test.Rd new file mode 100644 index 0000000..c26cc60 --- /dev/null +++ b/man/spd_download_test.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/download.r +\name{spd_download_test} +\alias{spd_download_test} +\title{Download test} +\usage{ +spd_download_test(server, config = NULL) +} +\description{ +Download test +} diff --git a/man/spd_servers.Rd b/man/spd_servers.Rd new file mode 100644 index 0000000..c79a10b --- /dev/null +++ b/man/spd_servers.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/servers.r +\name{spd_servers} +\alias{spd_servers} +\title{Retrieve a list of SpeedTest servers} +\usage{ +spd_servers(config = NULL) +} +\arguments{ +\item{config}{client configuration retrieved via [spd_config()]. If `NULL` it +will be retrieved} +} +\value{ +data frame +} +\description{ +Retrieve a list of SpeedTest servers +} diff --git a/man/speedtest.Rd b/man/speedtest.Rd new file mode 100644 index 0000000..0653312 --- /dev/null +++ b/man/speedtest.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/speedtest-package.R +\docType{package} +\name{speedtest} +\alias{speedtest} +\alias{speedtest-package} +\title{...} +\description{ +... +} +\author{ +Bob Rudis (bob@rud.is) +} diff --git a/speedtest.Rproj b/speedtest.Rproj new file mode 100644 index 0000000..446d9e1 --- /dev/null +++ b/speedtest.Rproj @@ -0,0 +1,21 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +StripTrailingWhitespace: Yes + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageBuildArgs: --resave-data +PackageRoxygenize: rd,collate,namespace diff --git a/tests/test-all.R b/tests/test-all.R new file mode 100644 index 0000000..2826f0b --- /dev/null +++ b/tests/test-all.R @@ -0,0 +1,2 @@ +library(testthat) +test_check("speedtest") diff --git a/tests/testthat/test-speedtest.R b/tests/testthat/test-speedtest.R new file mode 100644 index 0000000..ab6f62f --- /dev/null +++ b/tests/testthat/test-speedtest.R @@ -0,0 +1,6 @@ +context("basic functionality") +test_that("we can do something", { + + #expect_that(some_function(), is_a("data.frame")) + +})