示例#1
0
 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
示例#2
0
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