Class: Speedtest::Test

Inherits:
Object
  • Object
show all
Defined in:
lib/speedtest.rb

Defined Under Namespace

Classes: FailedTransfer

Constant Summary collapse

HTTP_PING_TIMEOUT =
5

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Test

Returns a new instance of Test.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/speedtest.rb', line 18

def initialize(options = {})
  @min_transfer_secs = options[:min_transfer_secs] || 10
  @num_threads   = options[:num_threads]           || 4
  @ping_runs = options[:ping_runs]                 || 4
  @download_size = options[:download_size]         || 4000
  @upload_size = options[:upload_size]             || 1_000_000
  @logger = options[:logger]
  @num_transfers_padding = options[:num_transfers_padding] || 5

  if @num_transfers_padding > @num_threads
    @num_transfers_padding = @num_threads
  end
  @ping_runs = 2 if @ping_runs < 2
end

Instance Method Details

#downloadObject



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/speedtest.rb', line 71

def download
  log "\nstarting download tests:"

  start_time = Time.now
  futures_ring = Ring.new(@num_threads + @num_transfers_padding)
  download_url = "#{@server_root}/speedtest/random#{@download_size}x#{@download_size}.jpg"
  pool = DownloadWorker.pool(size: @num_threads, args: [download_url, @logger])
  1.upto(@num_threads + @num_transfers_padding).each do |i|
    futures_ring.append(pool.future.download)
  end

  total_downloaded = 0
  while (future = futures_ring.pop) do
    status = future.value
    raise FailedTransfer.new("Download failed.") if status.error == true
    total_downloaded += status.size

    if Time.now - start_time < @min_transfer_secs
      futures_ring.append(pool.future.download)
    end
  end

  total_time = Time.new - start_time
  log "Took #{total_time} seconds to download #{total_downloaded} bytes in #{@num_threads} threads\n"

  [ total_downloaded * 8, total_time ]
end

#error(msg) ⇒ Object



67
68
69
# File 'lib/speedtest.rb', line 67

def error(msg)
  @logger.error msg if @logger
end

#log(msg) ⇒ Object



63
64
65
# File 'lib/speedtest.rb', line 63

def log(msg)
  @logger.debug msg if @logger
end

#pick_serverObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/speedtest.rb', line 135

def pick_server
  page = HTTParty.get("http://www.speedtest.net/speedtest-config.php")
  ip,lat,lon = page.body.scan(/<client ip="([^"]*)" lat="([^"]*)" lon="([^"]*)"/)[0]
  orig = GeoPoint.new(lat, lon)
  log "Your IP: #{ip}\nYour coordinates: #{orig}\n"

  page = HTTParty.get("http://www.speedtest.net/speedtest-servers.php")
  sorted_servers=page.body.scan(/<server url="([^"]*)" lat="([^"]*)" lon="([^"]*)/).map { |x| {
    :distance => orig.distance(GeoPoint.new(x[1],x[2])),
    :url => x[0].split(/(http:\/\/.*)\/speedtest.*/)[1]
  } }
  .reject { |x| x[:url].nil? } # reject 'servers' without a domain
  .sort_by { |x| x[:distance] }

  # sort the nearest 10 by download latency
  latency_sorted_servers = sorted_servers[0..9].map { |x|
    {
    :latency => ping(x[:url]),
    :url => x[:url]
    }}.sort_by { |x| x[:latency] }
  selected = latency_sorted_servers[0]
  log "Automatically selected server: #{selected[:url]} - #{selected[:latency]} ms"

  selected
end

#ping(server) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/speedtest.rb', line 161

def ping(server)
  times = []
  1.upto(@ping_runs) {
    start = Time.new
    begin
      page = HTTParty.get("#{server}/speedtest/latency.txt", timeout: HTTP_PING_TIMEOUT)
      times << Time.new - start
    rescue Timeout::Error, Net::HTTPNotFound, Errno::ENETUNREACH => e
      log "ping error: #{e.class} [#{e}] for #{server}"
      times << 999999
    end
  }
  times.sort
  times[1, @ping_runs].inject(:+) * 1000 / @ping_runs # average in milliseconds
end

#pretty_speed(speed) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/speedtest.rb', line 53

def pretty_speed(speed)
  units = ["bps", "Kbps", "Mbps", "Gbps", "Tbps"]
  i = 0
  while speed > 1024
    speed /= 1024
    i += 1
  end
  "%.2f #{units[i]}" % speed
end

#randomString(alphabet, size) ⇒ Object



99
100
101
# File 'lib/speedtest.rb', line 99

def randomString(alphabet, size)
  (1.upto(size)).map { alphabet[rand(alphabet.length)] }.join
end

#runObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/speedtest.rb', line 33

def run()
  server = pick_server
  @server_root = server[:url]
  log "Server #{@server_root}"

  latency = server[:latency]

  download_size, download_time = download
  download_rate = download_size / download_time
  log "Download: #{pretty_speed download_rate}"

  upload_size, upload_time = upload
  upload_rate = upload_size / upload_time
  log "Upload: #{pretty_speed upload_rate}"

  Result.new(:server => @server_root, :latency => latency,
    download_size: download_size, download_time: download_time,
    upload_size: upload_size, upload_time: upload_time)
end

#uploadObject



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/speedtest.rb', line 103

def upload
  log "\nstarting upload tests:"

  data = randomString(('A'..'Z').to_a, @upload_size)

  start_time = Time.now

  futures_ring = Ring.new(@num_threads + @num_transfers_padding)
  upload_url = "#{@server_root}/speedtest/upload.php"
  pool = UploadWorker.pool(size: @num_threads, args: [upload_url, @logger])
  1.upto(@num_threads + @num_transfers_padding).each do |i|
    futures_ring.append(pool.future.upload(data))
  end

  total_uploaded = 0
  while (future = futures_ring.pop) do
    status = future.value
    raise FailedTransfer.new("Upload failed.") if status.error == true
    total_uploaded += status.size

    if Time.now - start_time < @min_transfer_secs
      futures_ring.append(pool.future.upload(data))
    end
  end

  total_time = Time.new - start_time
  log "Took #{total_time} seconds to upload #{total_uploaded} bytes in #{@num_threads} threads\n"

  # bytes to bits / time = bps
  [ total_uploaded * 8, total_time ]
end