Инструмент бенчмаркинга с использованием Twisted

Я пытаюсь написать инструмент веб-бенчмаркинга на основе Twisted. Twisted — фантастический асинхронный фреймворк для веб-приложений. Поскольку я начал работать с этим фреймворком всего две недели, я столкнулся с проблемой, вот она: когда я тестирую этот инструмент сравнительного анализа по сравнению с ApacheBench, результаты сильно различаются при одном и том же параллелизме. Вот результат моего инструмента:

python pyab.py 50000 50 http://xx.com/a.txt

speed:1063(q/s), worker:50, interval:7, req_made:7493, req_done:7443, req_error:0

И вот результат Apache Bench:

ab -c 50 -n 50000 http://xx.com/a.txt

Server Software:        nginx/1.4.1
Server Hostname:        203.90.245.26
Server Port:            8080

Document Path:          /a.txt
Document Length:        6 bytes

Concurrency Level:      50
Time taken for tests:   6.89937 seconds
Complete requests:      50000
Failed requests:        0
Write errors:           0
Total transferred:      12501750 bytes
HTML transferred:       300042 bytes
Requests per second:    8210.27 [#/sec] (mean)
Time per request:       6.090 [ms] (mean)
Time per request:       0.122 [ms] (mean, across all concurrent requests)
Transfer rate:          2004.62 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.8      0       4
Processing:     1    5   3.4      5     110
Waiting:        0    2   3.6      2     109
Total:          1    5   3.5      5     110

Percentage of the requests served within a certain time (ms)
  50%      5
  66%      6
  75%      6
  80%      6
  90%      7
  95%      7
  98%      8
  99%      8
 100%    110 (longest request)

При том же URL-адресе и параллелизме ApacheBench может достигать 8000 запросов в секунду, а pyab — только 1000 запросов в секунду.

Вот мой код (pyab.py):

from twisted.internet import reactor,threads
from twisted.internet.protocol import Protocol
from twisted.internet.defer import Deferred
from twisted.web.client import Agent
from twisted.web.client import HTTPConnectionPool
from twisted.web.http_headers import Headers
from twisted.python import log
import time, os, stat, logging, sys
from collections import Counter

logging.basicConfig(
    #filename= "/%s/log/%s.%s" % (RUN_DIR,RUN_MODULE,RUN_TIME),
    format="%(asctime)s [%(levelname)s] %(message)s",
    level=logging.WARNING,
    #level=logging.DEBUG,
    stream=sys.stdout
)

#log.startLogging(sys.stdout)
observer = log.PythonLoggingObserver()
observer.start()


class IgnoreBody(Protocol):
    def __init__(self, deferred, tl):
        self.deferred = deferred
        self.tl = tl

    def dataReceived(self, bytes):
        pass

    def connectionLost(self, reason):
        self.deferred.callback(None)


class Pyab:

    def __init__( self, n = 50000, concurrency = 100, url='http://203.90.245.26:8080/a.txt'):
        self.n = n
        self.url = url
        self.pool = HTTPConnectionPool(reactor, persistent=True)
        self.pool.maxPersistentPerHost = concurrency
        self.agent = Agent(reactor, connectTimeout = 5, pool = self.pool)
        #self.agent = Agent(reactor, connectTimeout = 5)

        self.time_start = time.time()
        self.max_worker = concurrency

        self.cnt = Counter({
            'worker' : 0 ,
            'req_made' : 0,
            'req_done' : 0,
            'req_error' : 0,
            })


    def monitor( self ):
        interval = int(time.time() - self.time_start)
        speed = 0
        if interval != 0:
            speed = int( self.cnt['req_done'] / interval )

        log.msg("speed:%d(q/s), worker:%d, interval:%d, req_made:%d, req_done:%d, req_error:%d" 
                % (speed, self.cnt['worker'], interval, self.cnt['req_made'], self.cnt['req_done'], self.cnt['req_error']), logLevel=logging.WARNING)


        reactor.callLater(1, lambda : self.monitor())

    def start( self ):
        self.keeprunning = True
        self.monitor()
        self.readMore()

    def stop( self ):
        self.keeprunning = False

    def readMore( self ):

        while self.cnt['worker'] < self.max_worker and self.cnt['req_done'] < self.n :
            self.make_request()

        if self.keeprunning and self.cnt['req_done'] < self.n:
            reactor.callLater( 0.0001, lambda: self.readMore() )
        else:
            reactor.stop()


    def make_request( self ):
        d = self.agent.request(
            'GET',
            #'http://examplexx.com/',
            #'http://example.com/',
            #'http://xa.xingcloud.com/v4/qvo/WDCXWD7500AADS-00M2B0_WD-WCAV5E38536685366?update0=ref0%2Ccor&update1=nation%2Ccn&action0=visit&_ts=1376397973636',
            #'http://203.90.245.26:8080/a.txt',
            self.url,
            Headers({'User-Agent': ['Twisted Web Client Example']}),
            None)

        self.cnt['worker'] += 1
        self.cnt['req_made'] += 1

        def cbResponse(resp):
            self.cnt['worker'] -= 1
            self.cnt['req_done'] += 1
            log.msg('response received')

            finished = Deferred()
            resp.deliverBody(IgnoreBody(finished, self))
            return finished

        def cbError(error):
            self.cnt['worker'] -= 1
            self.cnt['req_error'] += 1
            log.msg(error, logLevel=logging.ERROR)


        d.addCallback(cbResponse)
        d.addErrback(cbError)


if __name__ == '__main__' :
    if len(sys.argv) < 4:
        print "Usage: %s <n> <concurrency> <url>" % (sys.argv[0])
        sys.exit()

    ab = Pyab(n=int(sys.argv[1]), concurrency=int(sys.argv[2]), url=sys.argv[3])
    ab.start()
    reactor.run()

Что-то не так с моим кодом? Спасибо!


person closure    schedule 15.08.2013    source источник
comment
URL-адреса вашего текстового файла с результатами указывают на сайт, который, можно сказать, не совсем по теме.   -  person andrean    schedule 15.08.2013
comment
Это просто демонстрация, веб-сервер - это локальный веб-сервер.   -  person closure    schedule 15.08.2013
comment
о, извините, я думал, что это ссылки для скачивания ваших результатов ..   -  person andrean    schedule 15.08.2013
comment
Всегда используйте example.com, если вы собираетесь использовать доменное имя в качестве примера.   -  person Glyph    schedule 16.08.2013


Ответы (1)


Когда я в последний раз использовал его, было известно, что ab содержит десятки серьезных ошибок. Иногда это приводило к тому, что он сообщал о сильно завышенных результатах. Иногда он сообщал об отрицательных результатах. Иногда это давало сбой. Я бы попробовал другой инструмент, например httperf, для проверки работоспособности.

Однако, если ваш сервер действительно такой быстрый, у вас может быть другая проблема.

Даже если ab было исправлено, вы говорите здесь о программе C и программе Python, работающей на CPython. 8x медленнее, чем C в Python, на самом деле не так уж и плохо, поэтому я не ожидаю, что с вашей программой действительно что-то не так, за исключением того, что она не использует spawnProcess и многоядерный параллелизм.

Для начала посмотрите, получите ли вы лучшие результаты на PyPy.

person Glyph    schedule 15.08.2013
comment
Спасибо за ваши советы, они действительно помогают. Во-первых, возможно, ApacheBench дает мне неверный результат. Я буду использовать httperf и другие инструменты для тестирования, чтобы проверить это. Сенкод, причина того, что сервер такой быстрый, заключается в том, что он обслуживает только статический файл, который просто содержит приветствие. В-третьих, когда я запускаю два экземпляра pyab одновременно, оба они могут достигать 1000 (запросов в секунду). Я проверю все подсказки один за другим позже. Еще раз спасибо! - person closure; 16.08.2013