Переглянути джерело

Add aioworkers to Python (#7022)

* Add aioworkers to Python

* add README to aioworkers (Python)

* optimization requirements of aioworkers (Python)

* update aioworkers with random.seed (Python)

* update aioworkers database_updates with uniq rows (Python)

* update aioworkers catch Deadlock (Python)
Alexander Malev 3 роки тому
батько
коміт
6ff9b51f45

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

@@ -0,0 +1,34 @@
+# [Aioworkers](https://github.com/aioworkers) Benchmarking Test
+
+This is the aioworkers portion of a [benchmarking tests suite](../../)
+comparing a variety of web development platforms.
+
+The information below is specific to aioworkers. 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
+
+[aioworkers](https://github.com/aioworkers) is a configurable workers
+based on asyncio for Python 3.
+
+
+## Implementation
+
+Aioworkers is implemented using:
+
+* The uvloop event loop.
+* The httptools HTTP parsing library.
+
+
+## Test sources
+
+All of the test implementations are located within
+[config.yaml](config.yaml), [app.py](app.py), 
+[config-pg.yaml](config-pg.yaml) and [pg.py](pg.py).
+
+
+## Resources
+
+* [Repo](https://github.com/aioworkers)

+ 14 - 0
frameworks/Python/aioworkers/aioworkers-pypy.dockerfile

@@ -0,0 +1,14 @@
+FROM pypy:3.8-bullseye
+
+ADD ./requirements.txt /aioworkers/
+
+WORKDIR /aioworkers
+
+RUN pip3 install -U pip && \
+    pip3 install -r /aioworkers/requirements.txt
+
+ADD ./ /aioworkers
+
+EXPOSE 8080
+
+CMD aioworkers aioworkers.net.web --multiprocessing -c config.yaml

+ 18 - 0
frameworks/Python/aioworkers/aioworkers.dockerfile

@@ -0,0 +1,18 @@
+FROM python:3.9-alpine
+RUN apk add --no-cache coreutils make gcc
+RUN apk add --no-cache python3-dev musl-dev libffi-dev
+RUN pip3 install uvloop
+
+ADD ./requirements.txt /aioworkers/
+ADD ./requirements-pg.txt /aioworkers/
+
+WORKDIR /aioworkers
+
+RUN pip3 install -U pip && \
+    pip3 install -r /aioworkers/requirements-pg.txt
+
+ADD ./ /aioworkers
+
+EXPOSE 8080
+
+CMD aioworkers aioworkers.net.web --multiprocessing -c config.yaml -c config-pg.yaml

+ 6 - 0
frameworks/Python/aioworkers/app.py

@@ -0,0 +1,6 @@
+async def json_serialization():
+    return {"message": "Hello, world!"}
+
+
+async def plaintext_serialization():
+    return "Hello, world!"

+ 45 - 0
frameworks/Python/aioworkers/benchmark_config.json

@@ -0,0 +1,45 @@
+{
+  "framework": "aioworkers",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "fortune_url": "/fortunes",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "framework": "aioworkers",
+      "language": "Python",
+      "flavor": "Python3",
+      "platform": "None",
+      "webserver": "aioworkers.net.web",
+      "os": "Linux",
+      "orm": "Raw",
+      "database_os": "Linux",
+      "database": "Postgres",
+      "display_name": "aioworkers",
+      "notes": ""
+    },
+    "pypy": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "framework": "aioworkers",
+      "language": "Python",
+      "flavor": "PyPy3",
+      "platform": "None",
+      "webserver": "aioworkers.net.web",
+      "os": "Linux",
+      "orm": "Raw",
+      "database_os": "Linux",
+      "database": "Postgres",
+      "display_name": "aioworkers",
+      "notes": ""
+    }
+  }]
+}

+ 36 - 0
frameworks/Python/aioworkers/config-pg.yaml

@@ -0,0 +1,36 @@
+env:
+  pg.connection:
+    user: PGUSER
+    password: PGPASS
+
+web:
+  resources:
+    /db:
+      get: pg.single_database_query
+    /queries:
+      get: pg.multiple_database_queries
+    /fortunes:
+      get: pg.fortunes
+    /updates:
+      get: pg.database_updates
+
+pg:
+  cls: pg.PG
+  connection:
+    dsn: postgresql://tfb-database:5432/hello_world
+    username: benchmarkdbuser
+    password: benchmarkdbpass
+
+templates:
+  cls: pg.Templates
+  fortune: |
+    <!DOCTYPE html>
+    <html>
+    <head><title>Fortunes</title></head>
+    <body>
+    <table>
+    <tr><th>id</th><th>message</th></tr>
+    {% for fortune in fortunes %}<tr><td>{{ fortune[0] }}</td><td>{{ fortune[1]|e }}</td></tr>
+    {% endfor %}</table>
+    </body>
+    </html>

+ 34 - 0
frameworks/Python/aioworkers/config.yaml

@@ -0,0 +1,34 @@
+http:
+  groups: [web]
+
+processes:
+  web:
+    cpus: 1
+    groups: [web]
+
+web:
+  groups: [web]
+  resources:
+    /plaintext:
+      get: app.plaintext_serialization
+    /json:
+      get: app.json_serialization
+
+logging:
+  version: 1
+  disable_existing_loggers: false
+  root:
+    level: ERROR
+    handlers: [console]
+  formatters:
+    console:
+      format: >-
+        [%(asctime)s.%(msecs)03d]
+        [%(processName)s %(process)s]
+        [%(levelname)1.1s]:       %(message)s
+      datefmt: '%Y.%m.%d %H:%M:%S'
+  handlers:
+    console:
+      level: DEBUG
+      class: logging.StreamHandler
+      formatter: console

+ 114 - 0
frameworks/Python/aioworkers/pg.py

@@ -0,0 +1,114 @@
+import logging
+from operator import itemgetter
+from random import randint
+
+import asyncpg.exceptions
+import jinja2
+from aioworkers_pg.base import Connector
+
+from aioworkers.core.base import AbstractEntity
+from aioworkers.core.config import ValueExtractor
+from aioworkers.net.uri import URI
+
+READ_ROW_SQL = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
+WRITE_ROW_SQL = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ADDITIONAL_ROW = [0, "Additional fortune added at request time."]
+sort_fortunes_key = itemgetter(1)
+logger = logging.getLogger(__name__)
+
+
+class PG(Connector):
+    def set_config(self, config: ValueExtractor) -> None:
+        cfg = config.connection
+        dsn: URI = cfg.get_uri("dsn").with_auth(
+            username=cfg.get("username"),
+            password=cfg.get("password"),
+        )
+        super().set_config(config.new_child(dsn=dsn))
+
+
+class Templates(AbstractEntity):
+    fortune: jinja2.Template
+
+    def set_config(self, config):
+        super().set_config(config)
+        self.fortune = jinja2.Template(config.fortune)
+
+
+def get_num_queries(request):
+    query_count = request.url.query.get_int("queries")
+    if query_count is None:
+        return 1
+    elif query_count < 1:
+        return 1
+    elif query_count > 500:
+        return 500
+    return query_count
+
+
+async def single_database_query(context):
+    row_id = randint(1, 10000)
+
+    async with context.pg.pool.acquire() as connection:
+        number = await connection.fetchval(READ_ROW_SQL, row_id)
+
+    return {"id": row_id, "randomNumber": number}
+
+
+async def multiple_database_queries(context, request):
+    num_queries = get_num_queries(request)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    async with context.pg.pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({"id": row_id, "randomNumber": number})
+
+    return worlds
+
+
+async def fortunes(context, request):
+    async with context.pg.pool.acquire() as connection:
+        fortunes = await connection.fetch("SELECT * FROM Fortune")
+
+    fortunes.append(ADDITIONAL_ROW)
+    fortunes.sort(key=sort_fortunes_key)
+    content = context.templates.fortune.render(fortunes=fortunes)
+
+    return request.response(
+        content.encode(),
+        headers=[
+            ("Content-Type", "text/html; charset=utf-8"),
+        ],
+    )
+
+
+async def database_updates(context, request):
+    num_queries = get_num_queries(request)
+    uniq = {randint(1, 10000) for _ in range(num_queries)}
+    while len(uniq) < num_queries:
+        uniq.add(randint(1, 10000))
+    updates = [
+        (row_id, randint(1, 10000)) for row_id in uniq
+    ]
+    worlds = [
+        {"id": row_id, "randomNumber": number} for row_id, number in updates
+    ]
+
+    async with context.pg.pool.acquire() as connection:
+        statement = await connection.prepare(READ_ROW_SQL)
+        for row_id, number in updates:
+            await statement.fetchval(row_id)
+        for _ in range(99):
+            try:
+                await connection.executemany(WRITE_ROW_SQL, updates)
+            except asyncpg.exceptions.DeadlockDetectedError as e:
+                logger.debug('Deadlock %s', e)
+            else:
+                break
+        else:
+            worlds.clear()
+
+    return worlds

+ 5 - 0
frameworks/Python/aioworkers/requirements-pg.txt

@@ -0,0 +1,5 @@
+-r requirements.txt
+uvloop==0.16.0
+asyncpg==0.25.0
+aioworkers-pg==0.2.0
+Jinja2==3.0.3

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

@@ -0,0 +1,3 @@
+aioworkers==0.21a1
+httptools==0.3.0
+PyYAML==6.0