Skip to content

Commit 90ecd0c

Browse files
committed
Initial WIP
1 parent 46331af commit 90ecd0c

File tree

8 files changed

+297
-0
lines changed

8 files changed

+297
-0
lines changed

tests/performance/large_doc.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/performance/manage.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
"""Django's command-line utility for administrative tasks."""
3+
import os
4+
import sys
5+
6+
7+
def main():
8+
"""Run administrative tasks."""
9+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "perftest.settings")
10+
try:
11+
from django.core.management import execute_from_command_line
12+
except ImportError as exc:
13+
raise ImportError(
14+
"Couldn't import Django. Are you sure it's installed and "
15+
"available on your PYTHONPATH environment variable? Did you "
16+
"forget to activate a virtual environment?"
17+
) from exc
18+
execute_from_command_line(sys.argv)
19+
20+
21+
if __name__ == "__main__":
22+
main()

tests/performance/perftest/__init__.py

Whitespace-only changes.

tests/performance/perftest/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from django.db import models
2+
3+
4+
class SmallFlatModel(models.Model):
5+
field1 = models.CharField(max_length=100)
6+
field2 = models.CharField(max_length=100)
7+
field3 = models.CharField(max_length=100)
8+
field4 = models.CharField(max_length=100)
9+
field5 = models.CharField(max_length=100)
10+
field6 = models.CharField(max_length=100)
11+
field7 = models.CharField(max_length=100)
12+
field8 = models.IntegerField()
13+
field9 = models.IntegerField()
14+
field10 = models.IntegerField()
15+
field11 = models.IntegerField()
16+
field12 = models.IntegerField()
17+
field13 = models.IntegerField()
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Django settings for perftest project.
3+
4+
Generated by 'django-admin startproject' using Django 5.2.4.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/5.2/topics/settings/
8+
9+
For the full list of settings and their values, see
10+
https://docs.djangoproject.com/en/5.2/ref/settings/
11+
"""
12+
13+
from pathlib import Path
14+
15+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
16+
BASE_DIR = Path(__file__).resolve().parent.parent
17+
18+
19+
# Quick-start development settings - unsuitable for production
20+
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
21+
22+
# SECURITY WARNING: keep the secret key used in production secret!
23+
SECRET_KEY = "django-insecure-xle!u*(htb-zn^-*kap_3s5u1#sm8p#f%@j@-6j6+97%p*n_ho"
24+
25+
# SECURITY WARNING: don't run with debug turned on in production!
26+
DEBUG = True
27+
28+
ALLOWED_HOSTS = []
29+
30+
31+
# Application definition
32+
33+
INSTALLED_APPS = [
34+
"perftest"
35+
]
36+
37+
MIDDLEWARE = [
38+
]
39+
40+
TEMPLATES = [
41+
{
42+
"BACKEND": "django.template.backends.django.DjangoTemplates",
43+
"DIRS": [],
44+
"APP_DIRS": True,
45+
"OPTIONS": {
46+
"context_processors": [
47+
"django.template.context_processors.request",
48+
"django.contrib.auth.context_processors.auth",
49+
"django.contrib.messages.context_processors.messages",
50+
],
51+
},
52+
},
53+
]
54+
55+
WSGI_APPLICATION = "perftest.wsgi.application"
56+
57+
58+
# Database
59+
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
60+
61+
DATABASES = {
62+
"default": {
63+
"ENGINE": "django_mongodb_backend",
64+
"HOST": "mongodb://localhost:27017",
65+
"NAME": "benchmarking",
66+
"PORT": 27017,
67+
},
68+
}
69+
70+
71+
# Password validation
72+
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
73+
74+
AUTH_PASSWORD_VALIDATORS = [
75+
{
76+
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
77+
},
78+
{
79+
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
80+
},
81+
{
82+
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
83+
},
84+
{
85+
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
86+
},
87+
]
88+
89+
90+
# Internationalization
91+
# https://docs.djangoproject.com/en/5.2/topics/i18n/
92+
93+
LANGUAGE_CODE = "en-us"
94+
95+
TIME_ZONE = "UTC"
96+
97+
USE_I18N = True
98+
99+
USE_TZ = True
100+
101+
102+
# Static files (CSS, JavaScript, Images)
103+
# https://docs.djangoproject.com/en/5.2/howto/static-files/
104+
105+
STATIC_URL = "static/"
106+
107+
# Default primary key field type
108+
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
109+
110+
DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField"

tests/performance/perftest/tests.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import json
2+
import os
3+
import time
4+
import warnings
5+
from typing import List
6+
7+
from django.forms import model_to_dict
8+
from django.test import (
9+
TestCase,
10+
)
11+
from django.test.utils import isolate_apps
12+
13+
from .models import SmallFlatModel
14+
15+
OUTPUT_FILE = os.environ.get("OUTPUT_FILE")
16+
17+
NUM_ITERATIONS = 100
18+
MIN_ITERATION_TIME = 60
19+
MAX_ITERATION_TIME = 300
20+
NUM_DOCS = 10000
21+
22+
result_data: List = []
23+
24+
def tearDownModule():
25+
output = json.dumps(result_data, indent=4)
26+
if OUTPUT_FILE:
27+
with open(OUTPUT_FILE, "w") as opf:
28+
opf.write(output)
29+
else:
30+
print(output)
31+
32+
class Timer:
33+
def __enter__(self):
34+
self.start = time.monotonic()
35+
return self
36+
37+
def __exit__(self, *args):
38+
self.end = time.monotonic()
39+
self.interval = self.end - self.start
40+
41+
# Copied from the driver benchmarking suite.
42+
class PerformanceTest:
43+
dataset: str
44+
data_size: int
45+
46+
def setUp(self):
47+
self.setup_time = time.monotonic()
48+
49+
def tearDown(self):
50+
duration = time.monotonic() - self.setup_time
51+
# Remove "Test" so that TestMyTestName is reported as "MyTestName".
52+
name = self.__class__.__name__[4:]
53+
median = self.percentile(50)
54+
megabytes_per_sec = self.data_size / median / 1000000
55+
print(
56+
f"Completed {self.__class__.__name__} {megabytes_per_sec:.3f} MB/s, MEDIAN={self.percentile(50):.3f}s, "
57+
f"total time={duration:.3f}s, iterations={len(self.results)}"
58+
)
59+
result_data.append(
60+
{
61+
"info": {
62+
"test_name": name,
63+
},
64+
"metrics": [
65+
{
66+
"name": "megabytes_per_sec",
67+
"type": "MEDIAN",
68+
"value": megabytes_per_sec,
69+
"metadata": {
70+
"improvement_direction": "up",
71+
"measurement_unit": "megabytes_per_second",
72+
},
73+
},
74+
],
75+
}
76+
)
77+
78+
def before(self):
79+
pass
80+
81+
def do_task(self):
82+
raise NotImplementedError
83+
84+
def after(self):
85+
pass
86+
87+
def percentile(self, percentile):
88+
if hasattr(self, "results"):
89+
sorted_results = sorted(self.results)
90+
percentile_index = int(len(sorted_results) * percentile / 100) - 1
91+
return sorted_results[percentile_index]
92+
else:
93+
self.fail("Test execution failed")
94+
return None
95+
96+
def runTest(self):
97+
results = []
98+
start = time.monotonic()
99+
i = 0
100+
while True:
101+
i += 1
102+
self.before()
103+
with Timer() as timer:
104+
self.do_task()
105+
self.after()
106+
results.append(timer.interval)
107+
duration = time.monotonic() - start
108+
if duration > MIN_ITERATION_TIME and i >= NUM_ITERATIONS:
109+
break
110+
if duration > MAX_ITERATION_TIME:
111+
with warnings.catch_warnings():
112+
warnings.simplefilter("default")
113+
warnings.warn(
114+
f"{self.__class__.__name__} timed out after {MAX_ITERATION_TIME}s, completed {i}/{NUM_ITERATIONS} iterations."
115+
)
116+
117+
break
118+
119+
self.results = results
120+
121+
122+
123+
class SmallFlatModelTests(PerformanceTest, TestCase):
124+
dataset = "small_doc.json"
125+
126+
def setUp(self):
127+
super().setUp()
128+
with open(self.dataset, "r") as data:
129+
self.document = json.load(data)
130+
131+
field_names = [field.name for field in SmallFlatModel._meta.get_fields() if field.name != "id"]
132+
values = self.document.values()
133+
model = SmallFlatModel()
134+
for field_name, value in zip(field_names, values):
135+
setattr(model, field_name, value)
136+
model.save()
137+
138+
def testTest(self):
139+
print("woo!")
140+
141+
142+
143+
144+
145+

tests/performance/small_doc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"oggaoR4O":"miNVpaKW","Vxri7mmI":"CS5VwrwN","IQ8K4ZMG":"Oq5Csk1w","WzI8s1W0":"ZPm57dhu","E5Aj2zB3":"gxUpzIjg","3MXe8Wi7":"Smo9whci","PHPSSV51":"TW34kfzq","CxSCo4jD":55336395,"5C8GSDC5":41992681,"fC63DsLR":72188733,"l6e0U4bR":46660880,"TLRpkltp":3527055,"Ph9CZN5L":74094448}

tests/performance/tweet.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"text":"@wildfits you're not getting one.....","in_reply_to_status_id":22773233453,"retweet_count":null,"contributors":null,"created_at":"Thu Sep 02 19:38:18 +0000 2010","geo":null,"source":"web","coordinates":null,"in_reply_to_screen_name":"wildfits","truncated":false,"entities":{"user_mentions":[{"indices":[0,9],"screen_name":"wildfits","name":"Mairin Goetzinger","id":41832464}],"urls":[],"hashtags":[]},"retweeted":false,"place":null,"user":{"friends_count":179,"profile_sidebar_fill_color":"7a7a7a","location":"Minneapols, MN/Brookings SD","verified":false,"follow_request_sent":null,"favourites_count":0,"profile_sidebar_border_color":"a3a3a3","profile_image_url":"http://a1.twimg.com/profile_images/1110614677/Screen_shot_2010-08-25_at_10.12.40_AM_normal.png","geo_enabled":false,"created_at":"Sun Aug 17 00:23:13 +0000 2008","description":"graphic designer + foodie, with a love of music, movies, running, design, + the outdoors!","time_zone":"Mountain Time (US & Canada)","url":"http://jessiefarris.com/","screen_name":"jessiekf","notifications":null,"profile_background_color":"303030","listed_count":1,"lang":"en","profile_background_image_url":"http://a3.twimg.com/profile_background_images/133733613/Picture_4.png","statuses_count":1010,"following":null,"profile_text_color":"d9a980","protected":false,"show_all_inline_media":false,"profile_background_tile":true,"name":"Jessie Farris","contributors_enabled":false,"profile_link_color":"363636","followers_count":218,"id":15878015,"profile_use_background_image":true,"utc_offset":-25200},"favorited":false,"in_reply_to_user_id":41832464,"id":22824602300}

0 commit comments

Comments
 (0)