Skip to content

Commit

Permalink
Merge pull request #42 from artur-graniszewski/develop
Browse files Browse the repository at this point in the history
Release of 1.6.4
  • Loading branch information
artur-graniszewski committed Apr 16, 2017
2 parents 69b96b6 + d240285 commit 5227937
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 325 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog

## Version 1.6.4
- [Improvement] Performance tweaks in ZEUS Web Server, service throughput for large files increased by 800% (achieving speed of 900 megabytes per second)
- [Improvement] Minor code refactoring in ZEUS and ReactPHP integration layer
- [Improvement] Documentation update

## Version 1.6.3
- [Improvement] Changed image URLs to absolute in the README file

Expand Down
84 changes: 84 additions & 0 deletions README.md
Expand Up @@ -238,6 +238,90 @@ user@host:/var/www/zf-application/vendor/zeus-server/zf3-server$ make doc-build

After executing above command, ZEUS documentation can be found under the following URL: http://127.0.0.1:8080/

# Performance

Most of the ZEUS code was heavily optimized and thoroughly tested for speed and efficiency.

As the response times of most of ZEUS services dropped below **1 milisecond**, its common for ZEUS to handle more than **17,000 requests/second** on an average mobile Intel Core i7 processor:
```
Server Software:
Server Hostname: 127.0.0.1
Server Port: 7070
Document Path: /apigility-ui/img/ag-hero.png
Document Length: 0 bytes
Concurrency Level: 16
Time taken for tests: 2.515 seconds
Complete requests: 50000
Failed requests: 0
Keep-Alive requests: 49513
Total transferred: 6942208 bytes
HTML transferred: 0 bytes
Requests per second: 19883.55 [#/sec] (mean)
Time per request: 0.805 [ms] (mean)
Time per request: 0.050 [ms] (mean, across all concurrent requests)
Transfer rate: 2696.01 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 0 1 1.2 1 70
Waiting: 0 1 1.2 1 70
Total: 0 1 1.2 1 70
Percentage of the requests served within a certain time (ms)
50% 1
66% 1
75% 1
80% 1
90% 1
95% 2
98% 3
99% 4
100% 70 (longest request)
```

Or achieve transfer speeds higher than **8 Gbits/sec**:
```
Server Software:
Server Hostname: 127.0.0.1
Server Port: 7070
Document Path: /test.file.txt
Document Length: 1048576 bytes
Concurrency Level: 16
Time taken for tests: 51.878 seconds
Complete requests: 50000
Failed requests: 0
Keep-Alive requests: 49513
Total transferred: 52435892208 bytes
HTML transferred: 52428800000 bytes
Requests per second: 963.80 [#/sec] (mean)
Time per request: 16.601 [ms] (mean)
Time per request: 1.038 [ms] (mean, across all concurrent requests)
Transfer rate: 987060.80 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 1 17 5.5 16 419
Waiting: 1 5 4.9 4 403
Total: 1 17 5.5 16 419
Percentage of the requests served within a certain time (ms)
50% 16
66% 17
75% 17
80% 18
90% 20
95% 23
98% 30
99% 35
100% 419 (longest request)
```

# Requirements

## OS requirements
Expand Down
57 changes: 32 additions & 25 deletions docs/book/http-benchmark-example.md
Expand Up @@ -79,11 +79,18 @@ In this test, ZEUS Web Server served over **17,918** requests per second.

## Bandwidth test

In this scenario we will test the performance of 16 concurrent GET requests (the `-c 16` switches) using the keep-alive connections (`-k` switch) on a 19KB static file.
First, we will create a 1MB static file in `public` directory of the Zend Framework 3 application.

Command:
```
user@host:/var/www/zf-apigility-skeleton$ ab -n 50000 -c 16 -k http://127.0.0.1:7070/apigility-ui/img/ag-hero.png
user@host:/var/www/zf-apigility-skeleton$ dd if=/dev/zero of=public/test.file.txt bs=1 count=1 seek=1048575
```

Now, we lets test the performance of 16 concurrent GET requests (the `-c 16` switches) using the keep-alive connections (`-k` switch) on a newly generated static file.

Command:
```
user@host:/var/www/zf-apigility-skeleton$ ab -n 50000 -c 16 -k http://127.0.0.1:7070/test.file.txt
```

Output (on the _Intel Core i7_ processor with a _7200RPM HDD_):
Expand All @@ -110,41 +117,41 @@ Server Software:
Server Hostname: 127.0.0.1
Server Port: 7070
Document Path: /apigility-ui/img/ag-hero.png
Document Length: 19869 bytes
Document Path: /test.file.txt
Document Length: 1048576 bytes
Concurrency Level: 16
Time taken for tests: 3.465 seconds
Time taken for tests: 51.878 seconds
Complete requests: 50000
Failed requests: 0
Keep-Alive requests: 49514
Total transferred: 1000392224 bytes
HTML transferred: 993450000 bytes
Requests per second: 14428.10 [#/sec] (mean)
Time per request: 1.109 [ms] (mean)
Time per request: 0.069 [ms] (mean, across all concurrent requests)
Transfer rate: 281909.42 [Kbytes/sec] received
Keep-Alive requests: 49513
Total transferred: 52435892208 bytes
HTML transferred: 52428800000 bytes
Requests per second: 963.80 [#/sec] (mean)
Time per request: 16.601 [ms] (mean)
Time per request: 1.038 [ms] (mean, across all concurrent requests)
Transfer rate: 987060.80 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 1
Processing: 0 1 1.2 1 28
Waiting: 0 1 1.2 1 28
Total: 0 1 1.2 1 28
Processing: 1 17 5.5 16 419
Waiting: 1 5 4.9 4 403
Total: 1 17 5.5 16 419
Percentage of the requests served within a certain time (ms)
50% 1
66% 1
75% 1
80% 1
90% 2
95% 3
98% 4
99% 6
100% 28 (longest request)
50% 16
66% 17
75% 17
80% 18
90% 20
95% 23
98% 30
99% 35
100% 419 (longest request)
```

In this test setup, ZEUS Web Server achieved the speed of around **280** megabytes, or **2202** megabits (2 Gb) per second.
In this test setup, ZEUS Web Server sent *50* gigabytes of data, achieving a speed of around **980** megabytes, or **8085** megabits (8 Gbits) per second.

# Athletic tests

Expand Down
2 changes: 1 addition & 1 deletion src/Zeus/Module.php
Expand Up @@ -22,7 +22,7 @@ class Module implements
ConsoleUsageProviderInterface,
ConsoleBannerProviderInterface
{
const MODULE_VERSION = "1.6.3";
const MODULE_VERSION = "1.6.4";

protected static $overrideConfig = [];

Expand Down
10 changes: 9 additions & 1 deletion src/Zeus/ServerService/Http/Dispatcher/StaticFileDispatcher.php
Expand Up @@ -44,6 +44,7 @@ public function __construct(array $config, DispatcherInterface $anotherDispatche
/**
* @param Request $httpRequest
* @param Response $httpResponse
* @return void
*/
public function dispatch(Request $httpRequest, Response $httpResponse)
{
Expand All @@ -68,6 +69,12 @@ public function dispatch(Request $httpRequest, Response $httpResponse)

$httpResponse->setStatusCode($code);
$this->addHeadersForFile($httpResponse, $fileName);
if (is_callable([$httpResponse, 'setStream'])) {
$httpResponse->setStream(fopen($fileName, "r"));


return;
}
readfile($fileName);

return;
Expand All @@ -76,8 +83,9 @@ public function dispatch(Request $httpRequest, Response $httpResponse)
$code = is_dir($fileName) ? Response::STATUS_CODE_403 : Response::STATUS_CODE_404;

if ($this->anotherDispatcher) {
$this->anotherDispatcher->dispatch($httpRequest, $httpResponse);

return $this->anotherDispatcher->dispatch($httpRequest, $httpResponse);
return;
}

$httpResponse->setStatusCode($code);
Expand Down
89 changes: 74 additions & 15 deletions src/Zeus/ServerService/Http/Message/Message.php
@@ -1,8 +1,7 @@
<?php

namespace Zeus\ServerService\Http\Message;

use Zend\Http\Headers;
use React\Stream\Buffer;
use Zeus\ServerService\Http\Message\Helper\ChunkedEncoding;
use Zeus\ServerService\Http\Message\Helper\Header;
use Zeus\ServerService\Http\Message\Helper\PostData;
Expand All @@ -16,7 +15,7 @@
use Zend\Http\Header\ContentLength;
use Zend\Http\Header\TransferEncoding;
use Zend\Http\Header\Vary;
use Zend\Http\Response;
use Zend\Http\Response\Stream as Response;
use Zend\Validator\Hostname as HostnameValidator;

class Message implements MessageComponentInterface, HeartBeatMessageInterface
Expand Down Expand Up @@ -256,22 +255,33 @@ public function onMessage(ConnectionInterface $connection, $message)
*/
protected function dispatchRequest(ConnectionInterface $connection, $callback)
{
$this->requestPhase = static::REQUEST_PHASE_PROCESSING;
$flushable = false;
$exception = null;

try {
if ($this->requestPhase !== static::REQUEST_PHASE_PROCESSING) {
$flushable = true;
ob_start(function ($buffer) use ($connection) {
$this->sendResponse($connection, $buffer);
}, $this->bufferSize);
}

try {
$this->requestPhase = static::REQUEST_PHASE_PROCESSING;
$this->mapUploadedFiles($this->request);
$callback($this->request, $this->response);

$this->requestPhase = static::REQUEST_PHASE_SENDING;
ob_end_flush();
} catch (\Exception $exception) {
ob_end_clean();
throw $exception;

} catch (\Throwable $exception) {
ob_end_clean();

}

if ($flushable) {
ob_end_flush();
}

if ($exception) {
throw $exception;
}

Expand Down Expand Up @@ -385,7 +395,7 @@ protected function sendHeaders(ConnectionInterface $connection, & $buffer)

if ($requestPhase === static::REQUEST_PHASE_SENDING) {
$isCompressed = $this->enableCompressionIfSupported($request, $response, $requestPhase, $buffer);
if (!$isCompressed && !$isChunkedResponse) {
if (!$isCompressed && !$isChunkedResponse && !$responseHeaders->has('Content-Length')) {
$responseHeaders->addHeader(new ContentLength(strlen($buffer)));
}
} else {
Expand Down Expand Up @@ -447,9 +457,48 @@ protected function sendResponse(ConnectionInterface $connection, $buffer)
$this->request->setMetadata('remoteAddress', $connection->getRemoteAddress());
}

$isChunkedResponse = $this->response->getMetadata('isChunkedResponse');
$stream = $this->response->getStream();

if (!is_resource($stream)) {
$this->sendBody($connection, $buffer);

return '';
}

if ($this->isBodyAllowedInResponse($this->request)) {
$this->requestPhase = static::REQUEST_PHASE_PROCESSING;
if ($buffer) {
$this->sendBody($connection, $buffer);
}

while (!feof($stream)) {
$data = fread($stream, $this->bufferSize);
$this->sendBody($connection, $data);
/** @var Buffer $buffer */
//$buffer = $connection->getBuffer();
//$buffer->handleWrite();
}
$this->requestPhase = static::REQUEST_PHASE_SENDING;
}

$this->sendBody($connection, null);

$this->response->setStream(null);
fclose($stream);

return '';
}

/**
* @param ConnectionInterface $connection
* @param string $buffer
* @return $this
*/
protected function sendBody(ConnectionInterface $connection, $buffer)
{
if ($this->isBodyAllowedInResponse($this->request)) {
$isChunkedResponse = $this->response->getMetadata('isChunkedResponse');

if ($isChunkedResponse) {
$bufferSize = strlen($buffer);
if ($bufferSize > 0) {
Expand All @@ -467,10 +516,19 @@ protected function sendResponse(ConnectionInterface $connection, $buffer)
}
}

if ($this->requestPhase !== static::REQUEST_PHASE_SENDING) {
return '';
if ($this->requestPhase === static::REQUEST_PHASE_SENDING) {
return $this->finalizeRequest($connection);
}

return $this;
}

/**
* @param ConnectionInterface $connection
* @return $this
*/
protected function finalizeRequest(ConnectionInterface $connection)
{
if (is_callable($this->responseHandler)) {
$callback = $this->responseHandler;
$callback($this->request, $this->response);
Expand All @@ -480,14 +538,15 @@ protected function sendResponse(ConnectionInterface $connection, $buffer)
if (!$this->request->getMetadata('isKeepAliveConnection')) {
$this->onClose($connection);

return '';
return $this;
}

$this->keepAliveCount--;
$this->initNewRequest();
$this->restartKeepAliveTimer();
$this->requestPhase = static::REQUEST_PHASE_KEEP_ALIVE;
return '';

return $this;
}

/**
Expand Down

0 comments on commit 5227937

Please sign in to comment.