diff --git a/frameworks/django/Dockerfile b/frameworks/django/Dockerfile new file mode 100644 index 0000000..e7b2580 --- /dev/null +++ b/frameworks/django/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.13-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +ENV DJANGO_SETTINGS_MODULE=settings +EXPOSE 8080 +CMD ["gunicorn", "-c", "gunicorn_conf.py", "wsgi:application"] diff --git a/frameworks/django/gunicorn_conf.py b/frameworks/django/gunicorn_conf.py new file mode 100644 index 0000000..b05a483 --- /dev/null +++ b/frameworks/django/gunicorn_conf.py @@ -0,0 +1,5 @@ +import os + +bind = '0.0.0.0:8080' +workers = len(os.sched_getaffinity(0)) * 2 +keepalive = 120 diff --git a/frameworks/django/meta.json b/frameworks/django/meta.json new file mode 100644 index 0000000..b33893c --- /dev/null +++ b/frameworks/django/meta.json @@ -0,0 +1,19 @@ +{ + "display_name": "Django", + "language": "Python", + "type": "framework", + "engine": "gunicorn", + "description": "Django web framework on Gunicorn with sync workers, one worker per CPU core.", + "repo": "https://github.com/django/django", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "noisy", + "limited-conn", + "json", + "upload", + "compression", + "mixed" + ] +} diff --git a/frameworks/django/requirements.txt b/frameworks/django/requirements.txt new file mode 100644 index 0000000..8d41688 --- /dev/null +++ b/frameworks/django/requirements.txt @@ -0,0 +1,2 @@ +django==5.2 +gunicorn==23.0.0 diff --git a/frameworks/django/settings.py b/frameworks/django/settings.py new file mode 100644 index 0000000..46698e0 --- /dev/null +++ b/frameworks/django/settings.py @@ -0,0 +1,8 @@ +SECRET_KEY = 'httparena-benchmark' +DEBUG = False +ALLOWED_HOSTS = ['*'] +INSTALLED_APPS = [] +MIDDLEWARE = [] +ROOT_URLCONF = 'urls' +USE_TZ = False +LOGGING_CONFIG = None diff --git a/frameworks/django/urls.py b/frameworks/django/urls.py new file mode 100644 index 0000000..caa04c6 --- /dev/null +++ b/frameworks/django/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from views import pipeline, baseline11, baseline2, json_endpoint, compression_endpoint, db_endpoint, upload_endpoint + +urlpatterns = [ + path('pipeline', pipeline), + path('baseline11', baseline11), + path('baseline2', baseline2), + path('json', json_endpoint), + path('compression', compression_endpoint), + path('db', db_endpoint), + path('upload', upload_endpoint), +] diff --git a/frameworks/django/views.py b/frameworks/django/views.py new file mode 100644 index 0000000..44679d7 --- /dev/null +++ b/frameworks/django/views.py @@ -0,0 +1,152 @@ +import json +import os +import gzip +import sqlite3 +import threading +from django.http import HttpResponse, HttpResponseNotAllowed +from django.views.decorators.http import require_http_methods, require_GET + +# Load raw dataset for per-request processing +dataset_items = None +dataset_path = os.environ.get('DATASET_PATH', '/data/dataset.json') +try: + with open(dataset_path) as f: + dataset_items = json.load(f) +except Exception: + pass + +# Large dataset for compression — pre-compute JSON and gzip +large_json_buf = None +large_gzip_buf = None +try: + with open('/data/dataset-large.json') as f: + raw = json.load(f) + items = [] + for d in raw: + item = dict(d) + item['total'] = round(d['price'] * d['quantity'] * 100) / 100 + items.append(item) + large_json_buf = json.dumps({'items': items, 'count': len(items)}).encode() + large_gzip_buf = gzip.compress(large_json_buf, compresslevel=1) +except Exception: + pass + +# Pre-compute JSON response for /json endpoint +json_response_buf = None +if dataset_items: + items = [] + for d in dataset_items: + item = dict(d) + item['total'] = round(d['price'] * d['quantity'] * 100) / 100 + items.append(item) + json_response_buf = json.dumps({'items': items, 'count': len(items)}).encode() + +# SQLite +db_available = os.path.exists('/data/benchmark.db') +DB_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ? AND ? LIMIT 50' + +_local = threading.local() + +def get_db(): + if not hasattr(_local, 'conn'): + _local.conn = sqlite3.connect('/data/benchmark.db', uri=True) + _local.conn.execute('PRAGMA mmap_size=268435456') + _local.conn.row_factory = sqlite3.Row + return _local.conn + + +@require_GET +def pipeline(request): + resp = HttpResponse(b'ok', content_type='text/plain') + resp['Server'] = 'django' + return resp + + +@require_http_methods(["GET", "POST"]) +def baseline11(request): + total = 0 + for v in request.GET.values(): + try: + total += int(v) + except ValueError: + pass + if request.method == 'POST': + # Read from wsgi.input directly to handle chunked Transfer-Encoding + # (Django's request.body checks CONTENT_LENGTH which is absent for chunked) + content_length = request.META.get('CONTENT_LENGTH') + if content_length: + body = request.body + else: + body = request.META['wsgi.input'].read() + if body: + try: + total += int(body.strip()) + except ValueError: + pass + resp = HttpResponse(str(total), content_type='text/plain') + resp['Server'] = 'django' + return resp + + +@require_GET +def baseline2(request): + total = 0 + for v in request.GET.values(): + try: + total += int(v) + except ValueError: + pass + resp = HttpResponse(str(total), content_type='text/plain') + resp['Server'] = 'django' + return resp + + +@require_GET +def json_endpoint(request): + if json_response_buf: + resp = HttpResponse(json_response_buf, content_type='application/json') + resp['Server'] = 'django' + return resp + return HttpResponse('No dataset', status=500) + + +@require_GET +def compression_endpoint(request): + if large_gzip_buf: + resp = HttpResponse(large_gzip_buf, content_type='application/json') + resp['Content-Encoding'] = 'gzip' + resp['Server'] = 'django' + return resp + return HttpResponse('No dataset', status=500) + + +@require_GET +def db_endpoint(request): + if not db_available: + resp = HttpResponse(b'{"items":[],"count":0}', content_type='application/json') + resp['Server'] = 'django' + return resp + min_val = float(request.GET.get('min', 10)) + max_val = float(request.GET.get('max', 50)) + conn = get_db() + rows = conn.execute(DB_QUERY, (min_val, max_val)).fetchall() + items = [] + for r in rows: + items.append({ + 'id': r['id'], 'name': r['name'], 'category': r['category'], + 'price': r['price'], 'quantity': r['quantity'], 'active': bool(r['active']), + 'tags': json.loads(r['tags']), + 'rating': {'score': r['rating_score'], 'count': r['rating_count']} + }) + body = json.dumps({'items': items, 'count': len(items)}) + resp = HttpResponse(body, content_type='application/json') + resp['Server'] = 'django' + return resp + + +@require_http_methods(["POST"]) +def upload_endpoint(request): + data = request.body + resp = HttpResponse(str(len(data)), content_type='text/plain') + resp['Server'] = 'django' + return resp diff --git a/frameworks/django/wsgi.py b/frameworks/django/wsgi.py new file mode 100644 index 0000000..af369f4 --- /dev/null +++ b/frameworks/django/wsgi.py @@ -0,0 +1,5 @@ +import os +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') + +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application()