Skip to content

Commit 5532582

Browse files
remittorfranz1981
authored andcommitted
[Python] Added a benchmark test for the FastWSGI Python3 web framework (TechEmpower#8019)
1 parent 1ceb785 commit 5532582

16 files changed

+413
-1
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python
2+
import fastwsgi
3+
import falcon
4+
import re
5+
from db_orm import session, World, Fortune
6+
from helpers import load_template, FortuneTuple, generate_ids, sanitize
7+
from operator import attrgetter
8+
from random import randint
9+
10+
11+
# setup
12+
app = falcon.App()
13+
14+
server_info = 'FastWSGI+Falcon'
15+
16+
17+
# resource endpoints
18+
class JSONResource(object):
19+
def on_get(self, request, response):
20+
response.set_header('Server', server_info)
21+
response.media = {'message': "Hello, world!"}
22+
23+
24+
class SingleQuery(object):
25+
@session(serializable=False)
26+
def on_get(self, request, response):
27+
wid = randint(1, 10000)
28+
world = World[wid]
29+
response.set_header('Server', server_info)
30+
response.media = world.to_dict()
31+
32+
33+
class MultipleQueries(object):
34+
@session(serializable=False)
35+
def on_get(self, request, response, num):
36+
num = sanitize(num)
37+
worlds = [World[ident].to_dict() for ident in generate_ids(num)]
38+
response.set_header('Server', server_info)
39+
response.media = worlds
40+
41+
42+
class UpdateQueries(object):
43+
@session(serializable=False)
44+
def on_get(self, request, response, num):
45+
num = sanitize(num)
46+
ids = generate_ids(num)
47+
ids.sort()
48+
worlds = []
49+
for item in ids:
50+
world = World[item]
51+
world.randomNumber = randint(1, 10000)
52+
worlds.append({"id": world.id, "randomNumber": world.randomNumber})
53+
response.set_header('Server', server_info)
54+
response.media = worlds
55+
56+
57+
class Fortunes(object):
58+
_template = load_template()
59+
60+
@session(serializable=False)
61+
def on_get(self, request, response):
62+
fortunes = [FortuneTuple(id=f.id, message=f.message) for f in Fortune.select()]
63+
fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
64+
fortunes.sort(key=attrgetter("message"))
65+
content = self._template.render(fortunes=fortunes)
66+
response.set_header('Server', server_info)
67+
response.content_type = falcon.MEDIA_HTML
68+
response.text = content
69+
70+
71+
class PlaintextResource(object):
72+
def on_get(self, request, response):
73+
response.set_header('Server', server_info)
74+
response.content_type = falcon.MEDIA_TEXT
75+
response.text = 'Hello, world!'
76+
77+
78+
# register resources
79+
app.add_route("/json", JSONResource())
80+
app.add_route("/db", SingleQuery())
81+
app.add_route("/queries/{num}", MultipleQueries())
82+
app.add_route("/updates/{num}", UpdateQueries())
83+
app.add_route("/fortunes", Fortunes())
84+
app.add_route("/plaintext", PlaintextResource())
85+
86+
87+
if __name__ == "__main__":
88+
import os
89+
import multiprocessing
90+
91+
_is_travis = os.environ.get('TRAVIS') == 'true'
92+
93+
workers = int(multiprocessing.cpu_count())
94+
if _is_travis:
95+
workers = 2
96+
97+
host = '0.0.0.0'
98+
port = 8080
99+
100+
def run_app():
101+
fastwsgi.run(app, host=host, port=port, loglevel=0)
102+
103+
def create_fork():
104+
n = os.fork()
105+
# n greater than 0 means parent process
106+
if not n > 0:
107+
run_app()
108+
109+
# fork limiting the cpu count - 1
110+
for i in range(1, workers):
111+
create_fork()
112+
113+
run_app() # run app on the main process too :)

frameworks/Python/falcon/benchmark_config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,29 @@
214214
"display_name": "Falcon [ASGI/Tortoise]",
215215
"notes": "",
216216
"versus": "uvicorn"
217+
},
218+
"fastwsgi": {
219+
"json_url": "/json",
220+
"db_url": "/db",
221+
"query_url": "/queries/",
222+
"update_url": "/updates/",
223+
"fortune_url": "/fortunes",
224+
"plaintext_url": "/plaintext",
225+
"port": 8080,
226+
"approach": "Realistic",
227+
"classification": "Micro",
228+
"database": "Postgres",
229+
"framework": "Falcon",
230+
"language": "Python",
231+
"flavor": "Python3",
232+
"orm": "Full",
233+
"platform": "WSGI",
234+
"webserver": "FastWSGI",
235+
"os": "Linux",
236+
"database_os": "Linux",
237+
"display_name": "Falcon [FastWSGI]",
238+
"notes": "",
239+
"versus": "wsgi"
217240
}
218241
}]
219242
}

frameworks/Python/falcon/config.toml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,27 @@ os = "Linux"
213213
database.os = "Linux"
214214
display.name = "Falcon [ASGI/Tortoise]"
215215
notes = ""
216-
versus = "uvicorn"
216+
versus = "uvicorn"
217+
218+
[fastwsgi]
219+
json.url = "/json"
220+
db.url = "/db"
221+
query.url = "/queries/"
222+
update.url = "/updates/"
223+
fortune.url = "/fortunes"
224+
plaintext.url = "/plaintext"
225+
port = 8080
226+
approach = "Realistic"
227+
classification = "Micro"
228+
database = "Postgres"
229+
framework = "Falcon"
230+
language = "Python"
231+
flavor = "Python3"
232+
orm = "Full"
233+
platform = "WSGI"
234+
webserver = "FastWSGI"
235+
os = "Linux"
236+
database.os = "Linux"
237+
display.name = "Falcon [FastWSGI]"
238+
notes = ""
239+
versus = "wsgi"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.9-bullseye
2+
3+
RUN apt-get update; apt-get install libpq-dev python3-dev -y
4+
WORKDIR /falcon
5+
COPY ./ /falcon
6+
RUN pip3 install -U pip; pip3 install -r /falcon/requirements-fastwsgi.txt
7+
8+
EXPOSE 8080
9+
10+
CMD ["python3", "app_fastwsgi.py"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-r requirements.txt
2+
click==8.0.1
3+
fastwsgi=0.0.8
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# FastWSGI Benchmark Test
2+
3+
This is the FastWSGI portion of a [benchmarking tests suite](../../)
4+
comparing a variety of web development platforms.
5+
6+
The information below is specific to FastWSGI. For further guidance,
7+
review the [documentation](https://github.com/TechEmpower/FrameworkBenchmarks/wiki).
8+
Also note that there is additional information provided in
9+
the [Python README](../).
10+
11+
## Description
12+
13+
[FastWSGI](https://github.com/jamesroberts/fastwsgi) is a lightning fast
14+
asyncio server for Python 3 based on [libuv](https://github.com/libuv/libuv) library.
15+
16+
## Implementation
17+
18+
FastWSGI is implemented using:
19+
20+
* The [libuv](https://github.com/libuv/libuv) - multi-platform C library that provides support for asynchronous I/O based on event loops.
21+
* The [llhttp](https://github.com/nodejs/llhttp) - fastest HTTP parsing library.
22+
* The WSGI consumer interface, for interacting with the application layer.
23+
24+
## Test Paths & Sources
25+
26+
All of the test implementations are located within a single file ([app.py](app.py)).
27+
28+
* [JSON Serialization](app.py): "/json"
29+
* [Plaintext](app.py): "/plaintext"
30+
31+
## Resources
32+
33+
* [FastWSGI on Github](https://github.com/jamesroberts/fastwsgi)
34+
* [Benchmarks](https://github.com/jamesroberts/fastwsgi/blob/main/performance_benchmarks/PERFORMANCE.md)

frameworks/Python/fastwsgi/app.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os
2+
import fastwsgi
3+
4+
try:
5+
from ujson import dumps as jsonify
6+
except:
7+
from json import dumps as jsonify
8+
9+
10+
def app(environ, start_response):
11+
path = environ["PATH_INFO"]
12+
headers = [ ('Server', 'FastWSGI') ]
13+
14+
if path == "/json":
15+
headers.append( ('Content-Type', 'application/json') )
16+
start_response('200 OK', headers)
17+
return [ jsonify( {"message":"Hello, World!"} ).encode('utf8') ]
18+
19+
if path == "/plaintext":
20+
headers.append( ('Content-Type', 'text/plain') )
21+
start_response('200 OK', headers)
22+
return [ b'Hello, World!' ]
23+
24+
start_response('400 Bad Request', headers)
25+
return [ b'' ]
26+
27+
28+
if __name__ == "__main__":
29+
import multiprocessing
30+
31+
_is_travis = os.environ.get('TRAVIS') == 'true'
32+
33+
workers = int(multiprocessing.cpu_count())
34+
if _is_travis:
35+
workers = 2
36+
37+
host = '0.0.0.0'
38+
port = 3000
39+
40+
def run_app():
41+
fastwsgi.run(app, host, port, loglevel=0)
42+
43+
def create_fork():
44+
n = os.fork()
45+
# n greater than 0 means parent process
46+
if not n > 0:
47+
run_app()
48+
49+
# fork limiting the cpu count - 1
50+
for i in range(1, workers):
51+
create_fork()
52+
53+
run_app() # run app on the main process too :)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"framework": "fastwsgi",
3+
"tests": [{
4+
"default": {
5+
"json_url": "/json",
6+
"plaintext_url": "/plaintext",
7+
"port": 3000,
8+
"approach": "Realistic",
9+
"classification": "Micro",
10+
"database": "None",
11+
"framework": "None",
12+
"language": "Python",
13+
"flavor": "Python3",
14+
"orm": "Raw",
15+
"platform": "None",
16+
"webserver": "FastWSGI",
17+
"os": "Linux",
18+
"database_os": "Linux",
19+
"display_name": "FastWSGI",
20+
"notes": ""
21+
}
22+
}]
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[framework]
2+
name = "fastwsgi"
3+
4+
[main]
5+
urls.plaintext = "/plaintext"
6+
urls.json = "/json"
7+
approach = "Realistic"
8+
classification = "Micro"
9+
database = "None"
10+
database_os = "Linux"
11+
os = "Linux"
12+
orm = "Raw"
13+
platform = "None"
14+
webserver = "FastWSGI"
15+
versus = "None"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM python:3.11-bullseye
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY requirements.txt ./
6+
RUN apt-get update
7+
RUN pip install --no-cache-dir ujson
8+
RUN pip install --no-cache-dir -r requirements.txt
9+
10+
COPY . .
11+
12+
EXPOSE 3000
13+
14+
CMD python ./app.py

0 commit comments

Comments
 (0)