Browse Source

[Python] Added a benchmark test for the FastWSGI Python3 web framework (#8019)

Oleg S 2 years ago
parent
commit
5361dee061

+ 113 - 0
frameworks/Python/falcon/app_fastwsgi.py

@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+import fastwsgi
+import falcon
+import re
+from db_orm import session, World, Fortune
+from helpers import load_template, FortuneTuple, generate_ids, sanitize
+from operator import attrgetter
+from random import randint
+
+
+# setup
+app = falcon.App()
+
+server_info = 'FastWSGI+Falcon'
+
+
+# resource endpoints
+class JSONResource(object):
+    def on_get(self, request, response):
+        response.set_header('Server', server_info)
+        response.media = {'message': "Hello, world!"}
+
+
+class SingleQuery(object):
+    @session(serializable=False)
+    def on_get(self, request, response):
+        wid = randint(1, 10000)
+        world = World[wid]
+        response.set_header('Server', server_info)
+        response.media = world.to_dict()
+
+
+class MultipleQueries(object):
+    @session(serializable=False)
+    def on_get(self, request, response, num):
+        num = sanitize(num)
+        worlds = [World[ident].to_dict() for ident in generate_ids(num)]
+        response.set_header('Server', server_info)
+        response.media = worlds
+
+
+class UpdateQueries(object):
+    @session(serializable=False)
+    def on_get(self, request, response, num):
+        num = sanitize(num)
+        ids = generate_ids(num)
+        ids.sort()
+        worlds = []
+        for item in ids:
+            world = World[item]
+            world.randomNumber = randint(1, 10000)
+            worlds.append({"id": world.id, "randomNumber": world.randomNumber})
+        response.set_header('Server', server_info)
+        response.media = worlds
+
+
+class Fortunes(object):
+    _template = load_template()
+
+    @session(serializable=False)
+    def on_get(self, request, response):
+        fortunes = [FortuneTuple(id=f.id, message=f.message) for f in Fortune.select()]
+        fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
+        fortunes.sort(key=attrgetter("message"))
+        content = self._template.render(fortunes=fortunes)
+        response.set_header('Server', server_info)
+        response.content_type = falcon.MEDIA_HTML
+        response.text = content
+
+
+class PlaintextResource(object):
+    def on_get(self, request, response):
+        response.set_header('Server', server_info)
+        response.content_type = falcon.MEDIA_TEXT
+        response.text = 'Hello, world!'
+
+
+# register resources
+app.add_route("/json", JSONResource())
+app.add_route("/db", SingleQuery())
+app.add_route("/queries/{num}", MultipleQueries())
+app.add_route("/updates/{num}", UpdateQueries())
+app.add_route("/fortunes", Fortunes())
+app.add_route("/plaintext", PlaintextResource())
+
+
+if __name__ == "__main__":
+    import os
+    import multiprocessing
+
+    _is_travis = os.environ.get('TRAVIS') == 'true'
+
+    workers = int(multiprocessing.cpu_count())
+    if _is_travis:
+        workers = 2
+
+    host = '0.0.0.0'
+    port = 8080
+
+    def run_app():
+        fastwsgi.run(app, host=host, port=port, loglevel=0)
+
+    def create_fork():
+        n = os.fork()
+        # n greater than 0 means parent process
+        if not n > 0:
+            run_app()
+
+    # fork limiting the cpu count - 1
+    for i in range(1, workers):
+        create_fork()
+
+    run_app()  # run app on the main process too :)

+ 23 - 0
frameworks/Python/falcon/benchmark_config.json

@@ -214,6 +214,29 @@
       "display_name": "Falcon [ASGI/Tortoise]",
       "display_name": "Falcon [ASGI/Tortoise]",
       "notes": "",
       "notes": "",
       "versus": "uvicorn"
       "versus": "uvicorn"
+    },
+    "fastwsgi": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries/",
+      "update_url": "/updates/",
+      "fortune_url": "/fortunes",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Falcon",
+      "language": "Python",
+      "flavor": "Python3",
+      "orm": "Full",
+      "platform": "WSGI",
+      "webserver": "FastWSGI",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Falcon [FastWSGI]",
+      "notes": "",
+      "versus": "wsgi"
     }
     }
   }]
   }]
 }
 }

+ 24 - 1
frameworks/Python/falcon/config.toml

@@ -213,4 +213,27 @@ os = "Linux"
 database.os = "Linux"
 database.os = "Linux"
 display.name = "Falcon [ASGI/Tortoise]"
 display.name = "Falcon [ASGI/Tortoise]"
 notes = ""
 notes = ""
-versus = "uvicorn"
+versus = "uvicorn"
+
+[fastwsgi]
+json.url = "/json"
+db.url = "/db"
+query.url = "/queries/"
+update.url = "/updates/"
+fortune.url = "/fortunes"
+plaintext.url = "/plaintext"
+port = 8080
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+framework = "Falcon"
+language = "Python"
+flavor = "Python3"
+orm = "Full"
+platform = "WSGI"
+webserver = "FastWSGI"
+os = "Linux"
+database.os = "Linux"
+display.name = "Falcon [FastWSGI]"
+notes = ""
+versus = "wsgi"

+ 10 - 0
frameworks/Python/falcon/falcon-fastwsgi.dockerfile

@@ -0,0 +1,10 @@
+FROM python:3.9-bullseye
+
+RUN apt-get update; apt-get install libpq-dev python3-dev -y
+WORKDIR /falcon
+COPY ./ /falcon
+RUN pip3 install -U pip; pip3 install -r /falcon/requirements-fastwsgi.txt
+
+EXPOSE 8080
+
+CMD ["python3", "app_fastwsgi.py"]

+ 3 - 0
frameworks/Python/falcon/requirements-fastwsgi.txt

@@ -0,0 +1,3 @@
+-r requirements.txt
+click==8.0.1
+fastwsgi=0.0.8

+ 34 - 0
frameworks/Python/fastwsgi/README.md

@@ -0,0 +1,34 @@
+# FastWSGI Benchmark Test
+
+This is the FastWSGI portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to FastWSGI. For further guidance,
+review the [documentation](https://github.com/TechEmpower/FrameworkBenchmarks/wiki).
+Also note that there is additional information provided in
+the [Python README](../).
+
+## Description
+
+[FastWSGI](https://github.com/jamesroberts/fastwsgi) is a lightning fast
+asyncio server for Python 3 based on [libuv](https://github.com/libuv/libuv) library.
+
+## Implementation
+
+FastWSGI is implemented using:
+
+* The [libuv](https://github.com/libuv/libuv) - multi-platform C library that provides support for asynchronous I/O based on event loops.
+* The [llhttp](https://github.com/nodejs/llhttp) - fastest HTTP parsing library.
+* The WSGI consumer interface, for interacting with the application layer.
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+* [JSON Serialization](app.py): "/json"
+* [Plaintext](app.py): "/plaintext"
+
+## Resources
+
+* [FastWSGI on Github](https://github.com/jamesroberts/fastwsgi)
+* [Benchmarks](https://github.com/jamesroberts/fastwsgi/blob/main/performance_benchmarks/PERFORMANCE.md)

+ 53 - 0
frameworks/Python/fastwsgi/app.py

@@ -0,0 +1,53 @@
+import os
+import fastwsgi
+
+try:
+    from ujson import dumps as jsonify
+except:
+    from json import dumps as jsonify
+
+
+def app(environ, start_response):
+    path = environ["PATH_INFO"]
+    headers = [ ('Server', 'FastWSGI') ]
+    
+    if path == "/json":
+        headers.append( ('Content-Type', 'application/json') )
+        start_response('200 OK', headers)
+        return [ jsonify( {"message":"Hello, World!"} ).encode('utf8') ]
+
+    if path == "/plaintext":
+        headers.append( ('Content-Type', 'text/plain') )
+        start_response('200 OK', headers)
+        return [ b'Hello, World!' ]
+
+    start_response('400 Bad Request', headers)
+    return [ b'' ]
+
+
+if __name__ == "__main__":
+    import multiprocessing
+
+    _is_travis = os.environ.get('TRAVIS') == 'true'
+
+    workers = int(multiprocessing.cpu_count())
+    if _is_travis:
+        workers = 2
+
+    host = '0.0.0.0'
+    port = 3000
+
+    def run_app():
+        fastwsgi.run(app, host, port, loglevel=0)
+
+    def create_fork():
+        n = os.fork()
+        # n greater than 0 means parent process
+        if not n > 0:
+            run_app()
+
+    # fork limiting the cpu count - 1
+    for i in range(1, workers):
+        create_fork()
+
+    run_app()  # run app on the main process too :)

+ 23 - 0
frameworks/Python/fastwsgi/benchmark_config.json

@@ -0,0 +1,23 @@
+{
+    "framework": "fastwsgi",
+    "tests": [{
+        "default": {
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 3000,
+            "approach": "Realistic",
+            "classification": "Micro",
+            "database": "None",
+            "framework": "None",
+            "language": "Python",
+            "flavor": "Python3",
+            "orm": "Raw",
+            "platform": "None",
+            "webserver": "FastWSGI",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "FastWSGI",
+            "notes": ""
+        }
+    }]
+}

+ 15 - 0
frameworks/Python/fastwsgi/config.toml

@@ -0,0 +1,15 @@
+[framework]
+name = "fastwsgi"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "FastWSGI"
+versus = "None"

+ 14 - 0
frameworks/Python/fastwsgi/fastwsgi.dockerfile

@@ -0,0 +1,14 @@
+FROM python:3.11-bullseye
+
+WORKDIR /usr/src/app
+
+COPY requirements.txt ./
+RUN apt-get update
+RUN pip install --no-cache-dir ujson
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+
+EXPOSE 3000
+
+CMD python ./app.py

+ 3 - 0
frameworks/Python/fastwsgi/requirements.txt

@@ -0,0 +1,3 @@
+click==8.0.1
+ujson==5.7.0
+fastwsgi==0.0.8

+ 50 - 0
frameworks/Python/flask/app-fastwsgi.py

@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+from flask import Flask, make_response, jsonify
+import os
+import multiprocessing
+import logging
+import fastwsgi
+
+# setup
+app = Flask(__name__)
+app.config["JSONIFY_PRETTYPRINT_REGULAR"] = False
+
+
[email protected]("/json")
+def hello():
+    response = make_response(jsonify(message="Hello, World!"))
+    response.content_type = "application/json"
+    response.headers.set('Server', 'FastWSGI+Flask')
+    return response
+
[email protected]("/plaintext")
+def plaintext():
+    response = make_response(b"Hello, World!")
+    response.content_type = "text/plain"
+    response.headers.set('Server', 'FastWSGI+Flask')
+    return response
+
+
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = int(multiprocessing.cpu_count())
+if _is_travis:
+    workers = 2
+
+host = '0.0.0.0'
+port = 8080
+
+def run_app():
+    fastwsgi.run(app, host, port, loglevel=0)
+
+def create_fork():
+    n = os.fork()
+    # n greater than 0 means parent process
+    if not n > 0:
+        run_app()
+
+# fork limiting the cpu count - 1
+for i in range(1, workers):
+    create_fork()
+
+run_app()  # run app on the main process too :)

+ 19 - 0
frameworks/Python/flask/benchmark_config.json

@@ -158,6 +158,25 @@
       "notes": "",
       "notes": "",
       "versus": "wsgi",
       "versus": "wsgi",
       "tags": ["broken"]
       "tags": ["broken"]
+    },
+    "fastwsgi": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "flask",
+      "language": "Python",
+      "flavor": "Python3",
+      "orm": "Full",
+      "platform": "None",
+      "webserver": "FastWSGI",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Flask [FastWSGI]",
+      "notes": "",
+      "versus": "wsgi"
     }
     }
   }]
   }]
 }
 }

+ 13 - 0
frameworks/Python/flask/config.toml

@@ -111,3 +111,16 @@ orm = "Full"
 platform = "Meinheld"
 platform = "Meinheld"
 webserver = "None"
 webserver = "None"
 versus = "wsgi"
 versus = "wsgi"
+
+[fastwsgi]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Full"
+platform = "None"
+webserver = "FastWSGI"
+versus = "wsgi"

+ 13 - 0
frameworks/Python/flask/flask-fastwsgi.dockerfile

@@ -0,0 +1,13 @@
+FROM python:3.10-bullseye
+
+
+RUN apt-get update
+RUN apt-get install libpq-dev python3-dev -y
+ADD ./requirements-fastwsgi.txt /flask/requirements.txt
+RUN pip3 install -r /flask/requirements.txt
+ADD ./ /flask
+WORKDIR /flask
+
+EXPOSE 8080
+
+CMD python ./app-fastwsgi.py

+ 3 - 0
frameworks/Python/flask/requirements-fastwsgi.txt

@@ -0,0 +1,3 @@
+Flask==2.2.2
+click==8.0.1
+fastwsgi==0.0.8