performance improvements + a good amount of changes

This commit is contained in:
Nicholas Orlowsky 2023-10-16 14:36:17 -04:00
parent 89a6d9a528
commit 0649a28a28
No known key found for this signature in database
GPG key ID: BE7DF0188A405E2B
24 changed files with 2040744 additions and 70 deletions

52
CHANGELOG.md Normal file
View file

@ -0,0 +1,52 @@
# 0.1.0 Second Pre-Release
- Allowed multiple clients to be handled at once via multithreadding
- Enabled file cacheing in file_backend
- Added benchmarking utils
## Known Issues
- High resource utilization
- Content other than HTMl will be treated as `text/html`
## Benchmark Results
Each benchmark makes 1000 requests requesting a 21.9Mb file using
100 users to the webserver running in a Docker container.
```
=====[ Anthracite Benchmarking Tool ]=====
Requests : 1000
Users/Threads: 100
====[ anthracite ]=====
Average Response Time: 5.9561 seconds
p995 Response Time : 65.9016 seconds
p99 Response Time : 12.3729 seconds
p90 Response Time : 7.8775 seconds
p75 Response Time : 5.9906 seconds
p50 Response Time : 5.0612 seconds
Total Response Time : 5956.0972 seconds
====[ nginx ]=====
Average Response Time: 6.0074 seconds
p995 Response Time : 12.5307 seconds
p99 Response Time : 11.9421 seconds
p90 Response Time : 8.4831 seconds
p75 Response Time : 6.6225 seconds
p50 Response Time : 5.5751 seconds
Total Response Time : 6007.4155 seconds
====[ apache ]=====
Average Response Time: 5.9028 seconds
p995 Response Time : 10.7204 seconds
p99 Response Time : 10.0042 seconds
p90 Response Time : 6.9419 seconds
p75 Response Time : 6.3974 seconds
p50 Response Time : 5.8380 seconds
Total Response Time : 5902.8490 seconds
==========
Total Test Time : 188.6373 seconds
```
# 0.0.0 First Pre-Release
- Added file based backend
- Support for text/html

View file

@ -1,7 +1,7 @@
FROM alpine as build-env FROM alpine as build-env
RUN apk add --no-cache build-base RUN apk add --no-cache build-base
COPY . . COPY ./src/ .
RUN make build-release RUN make build-release
CMD ["/anthracite"] CMD ["/anthracite"]

View file

@ -1,6 +0,0 @@
#include "../http.cpp"
class backend {
public:
virtual http_response handle_request(http_request req) = 0;
};

View file

@ -0,0 +1,2 @@
FROM anthracite:latest
COPY ./www/ /www/

3
benchmark/apache.Dockerfile Executable file
View file

@ -0,0 +1,3 @@
FROM httpd:2.4
WORKDIR /usr/local/apache2/htdocs/
COPY ./www/ .

69
benchmark/benchmark.py Normal file
View file

@ -0,0 +1,69 @@
import requests
import time
import math
from concurrent.futures import ThreadPoolExecutor
from http.client import HTTPConnection
HTTPConnection._http_vsn_str = 'HTTP/1.0'
urls = { 'anthracite': 'http://localhost:8081/large.html','nginx': 'http://localhost:8082/large.html', 'apache': 'http://localhost:8083/large.html' }
num_requests = 1000
num_users = 100 # number of threads
response_times = {}
def percentile(N, percent, key=lambda x:x):
if not N:
return None
N.sort()
k = (len(N)-1) * percent
f = math.floor(k)
c = math.ceil(k)
if f == c:
return key(N[int(k)])
d0 = key(N[int(f)]) * (c-k)
d1 = key(N[int(c)]) * (k-f)
return d0+d1
# Define a function to make an HTTP request and measure the response time
def make_request(request_number, server_name):
start_time = time.time()
response = requests.get(urls[server_name])
end_time = time.time()
if response.status_code == 200:
response_time = end_time - start_time
response_times[server_name].append(response_time)
else:
print(f'Request {request_number}: Request failed with status code {response.status_code}')
print('=====[ Anthracite Benchmarking Tool ]=====')
print(f'Requests : {num_requests}')
print(f'Users/Threads: {num_users}\n\n')
start_all_time = time.time()
futures = []
for server_name in urls:
response_times[server_name] = []
with ThreadPoolExecutor(max_workers=num_users) as executor:
futures += [executor.submit(make_request, i + 1, server_name) for i in range(num_requests)]
# Wait for all requests to complete
for future in futures:
future.result()
end_all_time = time.time()
for server_name in urls:
print(f'====[ {server_name} ]=====')
average_response_time = sum(response_times[server_name]) / len(response_times[server_name])
total_response_time = sum(response_times[server_name])
print(f'Average Response Time: {average_response_time:.4f} seconds')
print(f'p995 Response Time : {percentile(response_times[server_name], .995):.4f} seconds')
print(f'p99 Response Time : {percentile(response_times[server_name], .99):.4f} seconds')
print(f'p90 Response Time : {percentile(response_times[server_name], .90):.4f} seconds')
print(f'p75 Response Time : {percentile(response_times[server_name], .75):.4f} seconds')
print(f'p50 Response Time : {percentile(response_times[server_name], .50):.4f} seconds')
print(f'Total Response Time : {total_response_time:.4f} seconds')
total_test_time = end_all_time - start_all_time
print('==========')
print(f'Total Test Time : {total_test_time:.4f} seconds')

View file

@ -0,0 +1,19 @@
services:
anthracite:
build:
context: .
dockerfile: anthracite.Dockerfile
ports:
- "8081:80"
nginx:
build:
context: .
dockerfile: nginx.Dockerfile
ports:
- "8082:80"
apache:
build:
context: .
dockerfile: apache.Dockerfile
ports:
- "8083:80"

View file

@ -0,0 +1,2 @@
FROM nginx:alpine
COPY ./www/ /usr/share/nginx/html

5
benchmark/rebuild-container.sh Executable file
View file

@ -0,0 +1,5 @@
cd ..
docker build . -t anthracite:latest
cd benchmark
docker build . -t benchmark-anthracite -f anthracite.Dockerfile
docker compose up -d

1020251
benchmark/www/large.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,52 +0,0 @@
#include "backends/file_backend.cpp"
#include <exception>
#include <fstream>
#include <iostream>
#include <netinet/in.h>
#include <sstream>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <unordered_map>
using namespace std;
void log_request_an_response(http_request req, http_response resp);
constexpr int default_port = 80;
void handle_client(anthracite_socket s, file_backend fb)
{
http_request req(s);
http_response resp = fb.handle_request(req);
log_request_an_response(req, resp);
s.send_message(resp.to_string());
s.close_conn();
}
int main(int argc, char** argv)
{
int port_number = default_port;
if (argc > 1) {
port_number = atoi(argv[1]);
}
cout << "Initializing Anthracite" << endl;
anthracite_socket s(port_number);
file_backend fb(false);
cout << "Initialization Complete" << endl;
cout << "Listening for HTTP connections on port " << port_number << endl;
while(true) {
s.wait_for_conn();
thread(handle_client, s, ref(fb)).detach();
}
exit(0);
}
void log_request_an_response(http_request req, http_response resp)
{
cout << "[" << resp.status_code() << " " + http_status_map.find(resp.status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << endl;
}

View file

@ -1,10 +1,10 @@
.PHONY: format lint build build-release build-docker run debug .PHONY: format lint build build-release build-docker run debug
build: build:
g++ main.cpp -g -o anthracite g++ main.cpp -g -o ./anthracite
build-release: build-release:
g++ main.cpp -O2 -o anthracite g++ main.cpp -O3 -march=native -o ./anthracite
build-docker: build-docker:
docker build . -t anthracite docker build . -t anthracite

7
src/backends/backend.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "../http.cpp"
#include <memory>
class backend {
public:
virtual unique_ptr<http_response> handle_request(http_request& req) = 0;
};

View file

@ -6,7 +6,7 @@ private:
unordered_map<string, string> file_cache; unordered_map<string, string> file_cache;
bool cache_enabled; bool cache_enabled;
http_response handle_request_nocache(http_request req) { unique_ptr<http_response> handle_request_nocache(http_request& req) {
string filename = req.path() == "/" ? "index.html" : req.path(); string filename = req.path() == "/" ? "index.html" : req.path();
filename = "./www/" + filename; filename = "./www/" + filename;
ifstream stream(filename); ifstream stream(filename);
@ -20,10 +20,10 @@ private:
stringstream buffer; stringstream buffer;
buffer << stream.rdbuf(); buffer << stream.rdbuf();
return { buffer.str(), status }; return make_unique<http_response>(buffer.str(), status);
} }
http_response handle_request_cache(http_request req) { unique_ptr<http_response> handle_request_cache(http_request& req) {
string filename = req.path() == "/" ? "/index.html" : req.path(); string filename = req.path() == "/" ? "/index.html" : req.path();
filename = "./www" + filename; filename = "./www" + filename;
auto file_info = file_cache.find(filename); auto file_info = file_cache.find(filename);
@ -35,7 +35,7 @@ private:
file_info = file_cache.find(filename); file_info = file_cache.find(filename);
} }
return { file_info->second, status }; return make_unique<http_response>(file_info->second, status);
} }
void populate_cache_dir(string dir) { void populate_cache_dir(string dir) {
@ -66,7 +66,7 @@ public:
} }
} }
http_response handle_request(http_request req) override { unique_ptr<http_response> handle_request(http_request& req) override {
return cache_enabled ? handle_request_cache(req) : handle_request_nocache(req); return cache_enabled ? handle_request_cache(req) : handle_request_nocache(req);
} }
}; };

View file

@ -4,6 +4,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include "socket.cpp"
#include <exception> #include <exception>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@ -12,8 +13,6 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "socket.cpp"
using namespace std; using namespace std;
constexpr int HTTP_HEADER_BYTES = 8190; constexpr int HTTP_HEADER_BYTES = 8190;
@ -235,7 +234,7 @@ private:
unordered_map<string, query_param> _query_params; // kinda goofy, whatever unordered_map<string, query_param> _query_params; // kinda goofy, whatever
public: public:
http_request(anthracite_socket s) http_request(anthracite_socket& s)
: _path("") : _path("")
{ {
string raw_data = s.recv_message(HTTP_HEADER_BYTES); string raw_data = s.recv_message(HTTP_HEADER_BYTES);

69
src/main.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "backends/file_backend.cpp"
#include <condition_variable>
#include <exception>
#include <fstream>
#include <iostream>
#include <mutex>
#include <netinet/in.h>
#include <sstream>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <unordered_map>
using namespace std;
void log_request_and_response(http_request& req, unique_ptr<http_response>& resp);
constexpr int default_port = 80;
int active_threads = 0;
mutex mtx;
condition_variable cv;
void handle_client(anthracite_socket s, file_backend& fb)
{
http_request req(s);
unique_ptr<http_response> resp = fb.handle_request(req);
log_request_and_response(req, resp);
s.send_message(resp->to_string());
resp.reset();
s.close_conn();
{
std::lock_guard<std::mutex> lock(mtx);
active_threads--;
}
cv.notify_one();
}
int main(int argc, char** argv)
{
int port_number = default_port;
if (argc > 1) {
port_number = atoi(argv[1]);
}
cout << "Initializing Anthracite" << endl;
anthracite_socket s(port_number);
file_backend fb(true);
cout << "Initialization Complete" << endl;
cout << "Listening for HTTP connections on port " << port_number << endl;
for (;;) {
s.wait_for_conn();
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return active_threads < 20; });
active_threads++;
thread(handle_client, s, ref(fb)).detach();
}
exit(0);
}
void log_request_and_response(http_request& req, unique_ptr<http_response>& resp)
{
cout << "[" << resp->status_code() << " " + http_status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << endl;
}

View file

@ -20,7 +20,7 @@ private:
socklen_t client_addr_len {}; socklen_t client_addr_len {};
public: public:
anthracite_socket(int port, int max_queue = 100) anthracite_socket(int port, int max_queue = 10)
: server_socket(socket(AF_INET, SOCK_STREAM, 0)) : server_socket(socket(AF_INET, SOCK_STREAM, 0))
, client_ip("") , client_ip("")
{ {

1020251
src/www/large.html Normal file

File diff suppressed because it is too large Load diff

3
src/www/test.html Normal file
View file

@ -0,0 +1,3 @@
<center>
<h1>Test Page!</h1>
</center>