def __init__(self, poller): ClientHTTP.__init__(self, poller) STATE.update("test_name", "speedtest") self.measurer = HeadlessMeasurer(self.poller) self.child = None self.streams = collections.deque() self.finished = False self.state = None
class ClientSpeedtest(ClientHTTP): def __init__(self, poller): ClientHTTP.__init__(self, poller) STATE.update("test_name", "speedtest") self.measurer = HeadlessMeasurer(self.poller) self.child = None self.streams = collections.deque() self.finished = False self.state = None def configure(self, conf, measurer=None): if measurer: LOG.info("* speedtest: ignoring upstream measurer") ClientHTTP.configure(self, conf, self.measurer) def connect_uri(self, uri=None, count=None): if not uri: uri = self.conf.get("speedtest.client.uri", "http://neubot.blupixel.net/") if not count: count = self.conf.get("speedtest.client.nconn", 1) LOG.info("* speedtest with %s" % uri) ClientHTTP.connect_uri(self, uri, count) def connection_ready(self, stream): self.streams.append(stream) if len(self.streams) == self.conf.get("speedtest.client.nconn", 1): self.update() # # When some sockets successfully connect and others do not, # the code in net/stream.py should close all the open sockets. # So we don't need to do gymnastics here. # def connection_failed(self, connector, exception): self.cleanup(message="connection failed") def connection_lost(self, stream): self.cleanup(message="connection lost") # # Here we close the idle sockets and we assume in-use # ones are closed either by got_response() or by the # remote HTTP server. # def cleanup(self, message=""): if not self.finished: self.finished = True if message: LOG.error("* speedtest: %s" % message) while self.streams: self.streams.popleft().close() self.measurer.stop() self.measurer = None self.child = None NOTIFIER.publish(TESTDONE) def got_response(self, stream, request, response): if self.finished: stream.close() return LOG.progress() if response.code not in ("200", "206"): stream.close() self.cleanup("bad response code") else: try: self.child.got_response(stream, request, response) except (KeyboardInterrupt, SystemExit): raise except: LOG.exception() stream.close() self.cleanup("unexpected exception") else: self.streams.append(stream) self.update() def update(self): if self.finished: return # # Decide whether we can transition to the next phase of # the speedtest or not. Fall through to next request if # needed, or return to the caller and rewind the stack. # ostate = self.state if not self.state: self.state = "negotiate" del QUEUE_HISTORY[:] elif self.state == "negotiate": if self.conf.get("speedtest.client.unchoked", False): LOG.complete("authorized to take the test\n") self.state = "latency" elif "speedtest.client.queuepos" in self.conf: queuepos = self.conf["speedtest.client.queuepos"] LOG.complete("waiting in queue, pos %s\n" % queuepos) STATE.update("negotiate", {"queue_pos": queuepos}) QUEUE_HISTORY.append(queuepos) elif self.state == "latency": tries = self.conf.get("speedtest.client.latency_tries", 10) if tries == 0: # Calculate average latency latency = self.conf["speedtest.client.latency"] latency = sum(latency) / len(latency) self.conf["speedtest.client.latency"] = latency # Advertise the result STATE.update("test_latency", utils.time_formatter(latency)) LOG.complete("done, %s\n" % utils.time_formatter(latency)) self.state = "download" else: self.conf["speedtest.client.latency_tries"] = tries - 1 elif self.state in ("download", "upload"): if len(self.streams) == self.conf.get("speedtest.client.nconn", 1): # Calculate average speed speed = self.conf["speedtest.client.%s" % self.state] elapsed = (max(map(lambda t: t[1], speed)) - min(map(lambda t: t[0], speed))) speed = sum(map(lambda t: t[2], speed)) / elapsed LOG.progress(".[%s,%s]." % (utils.time_formatter(elapsed), utils.speed_formatter(speed))) # # O(N) loopless adaptation to the channel w/ memory # TODO bittorrent/peer.py implements an enhanced version # of this algorithm, with a cap to the max number of # subsequent tests. In addition to that, the bittorrent # code also anticipates the update of target_bytes. # if elapsed > LO_THRESH: ESTIMATE[self.state] *= TARGET/elapsed self.conf["speedtest.client.%s" % self.state] = speed # Advertise STATE.update("test_%s" % self.state, utils.speed_formatter(speed)) LOG.complete("done, %s\n" % utils.speed_formatter(speed)) if self.state == "download": self.state = "upload" else: self.state = "collect" elif elapsed > LO_THRESH/3: del self.conf["speedtest.client.%s" % self.state] ESTIMATE[self.state] *= TARGET/elapsed else: del self.conf["speedtest.client.%s" % self.state] ESTIMATE[self.state] *= 2 else: # Wait for all pending requests to complete return elif self.state == "collect": LOG.complete() self.cleanup() return else: raise RuntimeError("Invalid state") # # Perform state transition and run the next phase of the # speedtest. Not all phases need to employ all the connection # with the upstream server. # if self.state == "negotiate": ctor, justone = ClientNegotiate, True elif self.state == "latency": ctor, justone = ClientLatency, True elif self.state == "download": ctor, justone = ClientDownload, False elif self.state == "upload": ctor, justone = ClientUpload, False elif self.state == "collect": ctor, justone = ClientCollect, True else: raise RuntimeError("Invalid state") if ostate != self.state: self.child = ctor(self.poller) self.child.configure(self.conf, self.measurer) self.child.host_header = self.host_header if self.state not in ("negotiate", "collect"): if ostate == "negotiate" and self.state == "latency": STATE.update("test_latency", "---", publish=False) STATE.update("test_download", "---", publish=False) STATE.update("test_upload", "---", publish=False) STATE.update("test", "speedtest") else: STATE.update(self.state) LOG.start("* speedtest: %s" % self.state) elif self.state == "negotiate": LOG.start("* speedtest: %s" % self.state) self.measurer.start(self.state) while self.streams: self.child.connection_ready(self.streams.popleft()) if justone: break