Browse Source

complete granian benchmarks (#7412)

Giovanni Barillari 3 years ago
parent
commit
38a10251e5

+ 137 - 0
frameworks/Python/granian/app_asgi.py

@@ -1,5 +1,31 @@
+import asyncio
+import os
+
+from operator import itemgetter
+from pathlib import Path
+from random import randint
+from urllib.parse import parse_qs
+
+import asyncpg
+import jinja2
 import orjson
 import orjson
 
 
+
+async def pg_setup():
+    global pool
+    pool = await asyncpg.create_pool(
+        user=os.getenv('PGUSER', 'benchmarkdbuser'),
+        password=os.getenv('PGPASS', 'benchmarkdbpass'),
+        database='hello_world',
+        host='tfb-database',
+        port=5432
+    )
+
+
+SQL_SELECT = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
+SQL_UPDATE = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ROW_ADD = [0, 'Additional fortune added at request time.']
+
 JSON_RESPONSE = {
 JSON_RESPONSE = {
     'type': 'http.response.start',
     'type': 'http.response.start',
     'status': 200,
     'status': 200,
@@ -7,6 +33,13 @@ JSON_RESPONSE = {
         [b'content-type', b'application/json'],
         [b'content-type', b'application/json'],
     ]
     ]
 }
 }
+HTML_RESPONSE = {
+    'type': 'http.response.start',
+    'status': 200,
+    'headers': [
+        [b'content-type', b'text/html; charset=utf-8'],
+    ]
+}
 PLAINTEXT_RESPONSE = {
 PLAINTEXT_RESPONSE = {
     'type': 'http.response.start',
     'type': 'http.response.start',
     'status': 200,
     'status': 200,
@@ -15,8 +48,29 @@ PLAINTEXT_RESPONSE = {
     ]
     ]
 }
 }
 
 
+pool = None
+key = itemgetter(1)
 json_dumps = orjson.dumps
 json_dumps = orjson.dumps
 
 
+with Path('templates/fortune.html').open('r') as f:
+    template = jinja2.Template(f.read())
+
+asyncio.get_event_loop().run_until_complete(pg_setup())
+
+
+def get_num_queries(scope):
+    try:
+        query_string = scope['query_string']
+        query_count = int(parse_qs(query_string)['queries'][0])
+    except (KeyError, IndexError, ValueError):
+        return 1
+
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
 
 
 async def route_json(scope, receive, send):
 async def route_json(scope, receive, send):
     await send(JSON_RESPONSE)
     await send(JSON_RESPONSE)
@@ -27,6 +81,85 @@ async def route_json(scope, receive, send):
     })
     })
 
 
 
 
+async def route_db(scope, receive, send):
+    row_id = randint(1, 10000)
+    connection = await pool.acquire()
+    try:
+        number = await connection.fetchval(SQL_SELECT, row_id)
+        world = {'id': row_id, 'randomNumber': number}
+    finally:
+        await pool.release(connection)
+
+    await send(JSON_RESPONSE)
+    await send({
+        'type': 'http.response.body',
+        'body': json_dumps(world),
+        'more_body': False
+    })
+
+
+async def route_queries(scope, receive, send):
+    num_queries = get_num_queries(scope)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    connection = await pool.acquire()
+    try:
+        statement = await connection.prepare(SQL_SELECT)
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({'id': row_id, 'randomNumber': number})
+    finally:
+        await pool.release(connection)
+
+    await send(JSON_RESPONSE)
+    await send({
+        'type': 'http.response.body',
+        'body': json_dumps(worlds),
+        'more_body': False
+    })
+
+
+async def route_fortunes(scope, receive, send):
+    connection = await pool.acquire()
+    try:
+        fortunes = await connection.fetch('SELECT * FROM Fortune')
+    finally:
+        await pool.release(connection)
+
+    fortunes.append(ROW_ADD)
+    fortunes.sort(key=key)
+    content = template.render(fortunes=fortunes).encode('utf-8')
+    await send(HTML_RESPONSE)
+    await send({
+        'type': 'http.response.body',
+        'body': content,
+        'more_body': False
+    })
+
+
+async def route_updates(scope, receive, send):
+    num_queries = get_num_queries(scope)
+    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+
+    connection = await pool.acquire()
+    try:
+        statement = await connection.prepare(SQL_SELECT)
+        for row_id, _ in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(SQL_UPDATE, updates)
+    finally:
+        await pool.release(connection)
+
+    await send(JSON_RESPONSE)
+    await send({
+        'type': 'http.response.body',
+        'body': json_dumps(worlds),
+        'more_body': False
+    })
+
+
 async def route_plaintext(scope, receive, send):
 async def route_plaintext(scope, receive, send):
     await send(PLAINTEXT_RESPONSE)
     await send(PLAINTEXT_RESPONSE)
     await send({
     await send({
@@ -47,6 +180,10 @@ async def handle_404(scope, receive, send):
 
 
 routes = {
 routes = {
     '/json': route_json,
     '/json': route_json,
+    '/db': route_db,
+    '/queries': route_queries,
+    '/fortunes': route_fortunes,
+    '/updates': route_updates,
     '/plaintext': route_plaintext
     '/plaintext': route_plaintext
 }
 }
 
 

+ 122 - 0
frameworks/Python/granian/app_rsgi.py

@@ -1,12 +1,59 @@
+import asyncio
+import os
+
+from operator import itemgetter
+from pathlib import Path
+from random import randint
+from urllib.parse import parse_qs
+
+import asyncpg
+import jinja2
 import orjson
 import orjson
 
 
 from granian.rsgi import Response
 from granian.rsgi import Response
 
 
+
+async def pg_setup():
+    global pool
+    pool = await asyncpg.create_pool(
+        user=os.getenv('PGUSER', 'benchmarkdbuser'),
+        password=os.getenv('PGPASS', 'benchmarkdbpass'),
+        database='hello_world',
+        host='tfb-database',
+        port=5432
+    )
+
+
+SQL_SELECT = 'SELECT "randomnumber", "id" FROM "world" WHERE id = $1'
+SQL_UPDATE = 'UPDATE "world" SET "randomnumber"=$1 WHERE id=$2'
+ROW_ADD = [0, 'Additional fortune added at request time.']
+
 JSON_HEADERS = {'content-type': 'application/json'}
 JSON_HEADERS = {'content-type': 'application/json'}
+HTML_HEADERS = {'content-type': 'text/html; charset=utf-8'}
 PLAINTEXT_HEADERS = {'content-type': 'text/plain; charset=utf-8'}
 PLAINTEXT_HEADERS = {'content-type': 'text/plain; charset=utf-8'}
 
 
+pool = None
+key = itemgetter(1)
 json_dumps = orjson.dumps
 json_dumps = orjson.dumps
 
 
+with Path('templates/fortune.html').open('r') as f:
+    template = jinja2.Template(f.read())
+
+asyncio.get_event_loop().run_until_complete(pg_setup())
+
+
+def get_num_queries(scope):
+    try:
+        query_count = int(parse_qs(scope.query_string)['queries'][0])
+    except (KeyError, IndexError, ValueError):
+        return 1
+
+    if query_count < 1:
+        return 1
+    if query_count > 500:
+        return 500
+    return query_count
+
 
 
 async def route_json(scope, receive):
 async def route_json(scope, receive):
     return Response(
     return Response(
@@ -15,6 +62,77 @@ async def route_json(scope, receive):
     )
     )
 
 
 
 
+async def route_db(scope, receive):
+    row_id = randint(1, 10000)
+    connection = await pool.acquire()
+    try:
+        number = await connection.fetchval(SQL_SELECT, row_id)
+        world = {'id': row_id, 'randomNumber': number}
+    finally:
+        await pool.release(connection)
+
+    return Response(
+        1, 200, JSON_HEADERS,
+        json_dumps(world), None, None
+    )
+
+
+async def route_queries(scope, receive):
+    num_queries = get_num_queries(scope)
+    row_ids = [randint(1, 10000) for _ in range(num_queries)]
+    worlds = []
+
+    connection = await pool.acquire()
+    try:
+        statement = await connection.prepare(SQL_SELECT)
+        for row_id in row_ids:
+            number = await statement.fetchval(row_id)
+            worlds.append({'id': row_id, 'randomNumber': number})
+    finally:
+        await pool.release(connection)
+
+    return Response(
+        1, 200, JSON_HEADERS,
+        json_dumps(worlds), None, None
+    )
+
+
+async def route_fortunes(scope, receive):
+    connection = await pool.acquire()
+    try:
+        fortunes = await connection.fetch('SELECT * FROM Fortune')
+    finally:
+        await pool.release(connection)
+
+    fortunes.append(ROW_ADD)
+    fortunes.sort(key=key)
+    content = template.render(fortunes=fortunes)
+    return Response(
+        2, 200, HTML_HEADERS,
+        None, content, None
+    )
+
+
+async def route_updates(scope, receive):
+    num_queries = get_num_queries(scope)
+    updates = [(randint(1, 10000), randint(1, 10000)) for _ in range(num_queries)]
+    worlds = [{'id': row_id, 'randomNumber': number} for row_id, number in updates]
+
+    connection = await pool.acquire()
+    try:
+        statement = await connection.prepare(SQL_SELECT)
+        for row_id, _ in updates:
+            await statement.fetchval(row_id)
+        await connection.executemany(SQL_UPDATE, updates)
+    finally:
+        await pool.release(connection)
+
+    return Response(
+        1, 200, JSON_HEADERS,
+        json_dumps(worlds), None, None
+    )
+
+
 async def route_plaintext(scope, receive):
 async def route_plaintext(scope, receive):
     return Response(
     return Response(
         2, 200, PLAINTEXT_HEADERS,
         2, 200, PLAINTEXT_HEADERS,
@@ -31,6 +149,10 @@ async def handle_404(scope, receive):
 
 
 routes = {
 routes = {
     '/json': route_json,
     '/json': route_json,
+    '/db': route_db,
+    '/queries': route_queries,
+    '/fortunes': route_fortunes,
+    '/updates': route_updates,
     '/plaintext': route_plaintext
     '/plaintext': route_plaintext
 }
 }
 
 

+ 8 - 0
frameworks/Python/granian/benchmark_config.json

@@ -3,7 +3,11 @@
   "tests": [{
   "tests": [{
     "default": {
     "default": {
       "json_url": "/json",
       "json_url": "/json",
+      "fortune_url": "/fortunes",
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
       "port": 8080,
       "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Platform",
       "classification": "Platform",
@@ -21,7 +25,11 @@
     },
     },
     "rsgi": {
     "rsgi": {
       "json_url": "/json",
       "json_url": "/json",
+      "fortune_url": "/fortunes",
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
       "port": 8080,
       "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Platform",
       "classification": "Platform",

+ 8 - 0
frameworks/Python/granian/config.toml

@@ -4,6 +4,10 @@ name = "granian"
 [main]
 [main]
 urls.plaintext = "/plaintext"
 urls.plaintext = "/plaintext"
 urls.json = "/json"
 urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 approach = "Realistic"
 classification = "Platform"
 classification = "Platform"
 database = "Postgres"
 database = "Postgres"
@@ -17,6 +21,10 @@ versus = "uvicorn"
 [rsgi]
 [rsgi]
 urls.plaintext = "/plaintext"
 urls.plaintext = "/plaintext"
 urls.json = "/json"
 urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 approach = "Realistic"
 classification = "Platform"
 classification = "Platform"
 database = "Postgres"
 database = "Postgres"

+ 1 - 1
frameworks/Python/granian/granian-rsgi.dockerfile

@@ -1,4 +1,4 @@
-FROM python:3.8
+FROM python:3.10-slim
 
 
 ADD ./ /granian
 ADD ./ /granian
 
 

+ 1 - 1
frameworks/Python/granian/granian.dockerfile

@@ -1,4 +1,4 @@
-FROM python:3.8
+FROM python:3.10-slim
 
 
 ADD ./ /granian
 ADD ./ /granian
 
 

+ 4 - 2
frameworks/Python/granian/requirements.txt

@@ -1,3 +1,5 @@
-granian==0.1.0a1
-orjson==3.6.8
+asyncpg==0.25.0
+granian==0.1.0a2
+jinja2==3.1.2
+orjson==3.7.2
 uvloop==0.16.0
 uvloop==0.16.0