diff --git a/.evergreen/config.yml b/.evergreen/config.yml index d59bfe079..75bd315ef 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -48,6 +48,64 @@ functions: args: - ./.evergreen/run-tests.sh + "run performance tests": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: "src" + include_expansions_in_env: [ "DRIVERS_TOOLS", "MONGODB_URI" ] + args: + - ./.evergreen/run-perf-tests.sh + + attach benchmark test results: + - command: attach.results + params: + file_location: src/report.json + + send dashboard data: + - command: subprocess.exec + params: + binary: bash + args: + - .evergreen/perf-submission-setup.sh + working_dir: src + include_expansions_in_env: [ + "requester", + "revision_order_id", + "project_id", + "version_id", + "build_variant", + "parsed_order_id", + "task_name", + "task_id", + "execution", + "is_mainline" + ] + type: test + - command: expansions.update + params: + file: src/expansion.yml + - command: subprocess.exec + params: + binary: bash + args: + - .evergreen/perf-submission.sh + working_dir: src + include_expansions_in_env: [ + "requester", + "revision_order_id", + "project_id", + "version_id", + "build_variant", + "parsed_order_id", + "task_name", + "task_id", + "execution", + "is_mainline" + ] + type: test + "teardown": - command: subprocess.exec params: @@ -67,6 +125,12 @@ tasks: commands: - func: "run unit tests" + - name: perf-tests + commands: + - func: "run performance tests" + - func: "attach benchmark test results" + - func: "send dashboard data" + buildvariants: - name: tests-6-noauth-nossl display_name: Run Tests 6.0 NoAuth NoSSL @@ -111,3 +175,11 @@ buildvariants: SSL: "ssl" tasks: - name: run-tests + + - name: performance-benchmarks + display_name: Performance Benchmarks + run_on: + - rhel90-dbx-perf-large + batchtime: 10080 + tasks: + - name: perf-tests diff --git a/.evergreen/perf-submission-setup.sh b/.evergreen/perf-submission-setup.sh new file mode 100755 index 000000000..ecb38751a --- /dev/null +++ b/.evergreen/perf-submission-setup.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# We use the requester expansion to determine whether the data is from a mainline evergreen run or not + +set -eu + +# shellcheck disable=SC2154 +if [ "${requester}" == "commit" ]; then + echo "is_mainline: true" >> expansion.yml +else + echo "is_mainline: false" >> expansion.yml +fi + +# We parse the username out of the order_id as patches append that in and SPS does not need that information +# shellcheck disable=SC2154 +echo "parsed_order_id: $(echo "${revision_order_id}" | awk -F'_' '{print $NF}')" >> expansion.yml diff --git a/.evergreen/perf-submission.sh b/.evergreen/perf-submission.sh new file mode 100755 index 000000000..f7c3ea666 --- /dev/null +++ b/.evergreen/perf-submission.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# We use the requester expansion to determine whether the data is from a mainline evergreen run or not + +set -eu + +# Submit the performance data to the SPS endpoint +# shellcheck disable=SC2154 +response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X 'POST' \ + "https://performance-monitoring-api.corp.mongodb.com/raw_perf_results/cedar_report?project=${project_id}&version=${version_id}&variant=${build_variant}&order=${parsed_order_id}&task_name=${task_name}&task_id=${task_id}&execution=${execution}&mainline=${is_mainline}" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d @results.json) + +http_status=$(echo "$response" | grep "HTTP_STATUS" | awk -F':' '{print $2}') +response_body=$(echo "$response" | sed '/HTTP_STATUS/d') + +# We want to throw an error if the data was not successfully submitted +if [ "$http_status" -ne 200 ]; then + echo "Error: Received HTTP status $http_status" + echo "Response Body: $response_body" + exit 1 +fi + +echo "Response Body: $response_body" +echo "HTTP Status: $http_status" diff --git a/.evergreen/run-perf-tests.sh b/.evergreen/run-perf-tests.sh new file mode 100644 index 000000000..fab988c98 --- /dev/null +++ b/.evergreen/run-perf-tests.sh @@ -0,0 +1,15 @@ +#!/usr/bin/bash + +set -eux + +export OUTPUT_FILE="results.json" + +# Install django-mongodb-backend +/opt/python/3.10/bin/python3 -m venv venv +. venv/bin/activate +python -m pip install -U pip +pip install -e . + +python .evergreen/run_perf_test.py +mv tests/performance/$OUTPUT_FILE $OUTPUT_FILE +mv tests/performance/report.json report.json diff --git a/.evergreen/run_perf_test.py b/.evergreen/run_perf_test.py new file mode 100644 index 000000000..52ac431fd --- /dev/null +++ b/.evergreen/run_perf_test.py @@ -0,0 +1,55 @@ +import json +import logging +import os +import shlex +import subprocess +import sys +from datetime import datetime + +LOGGER = logging.getLogger("test") +logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") +OUTPUT_FILE = os.environ.get("OUTPUT_FILE") + + +def handle_perf(start_time: datetime): + end_time = datetime.now() + elapsed_secs = (end_time - start_time).total_seconds() + with open(OUTPUT_FILE) as fid: # noqa: PTH123 + results = json.load(fid) + LOGGER.info("results.json:\n%s", json.dumps(results, indent=2)) + + results = { + "status": "PASS", + "exit_code": 0, + "test_file": "BenchMarkTests", + "start": int(start_time.timestamp()), + "end": int(end_time.timestamp()), + "elapsed": elapsed_secs, + } + report = {"results": [results]} + + LOGGER.info("report.json\n%s", json.dumps(report, indent=2)) + + with open("report.json", "w", newline="\n") as fid: # noqa: PTH123 + json.dump(report, fid) + + +def run_command(cmd: str | list[str], **kwargs) -> None: + if isinstance(cmd, list): + cmd = " ".join(cmd) + LOGGER.info("Running command '%s'...", cmd) + kwargs.setdefault("check", True) + try: + subprocess.run(shlex.split(cmd), **kwargs) # noqa: PLW1510, S603 + except subprocess.CalledProcessError as e: + LOGGER.error(e.output) + LOGGER.error(str(e)) + sys.exit(e.returncode) + LOGGER.info("Running command '%s'... done.", cmd) + + +os.chdir("tests/performance") + +start_time = datetime.now() +run_command(["python manage.py test"]) +handle_perf(start_time) diff --git a/.github/workflows/runtests.py b/.github/workflows/runtests.py index 9e4096b55..40539190f 100755 --- a/.github/workflows/runtests.py +++ b/.github/workflows/runtests.py @@ -157,7 +157,8 @@ x.name for x in (pathlib.Path(__file__).parent.parent.parent.resolve() / "tests").iterdir() # Omit GIS tests unless GIS libraries are installed. - if x.name != "gis_tests_" + # Always omit the performance benchmarking suite. + if x.name != "gis_tests_" and x.name != "performance" ] ), ] diff --git a/tests/performance/manage.py b/tests/performance/manage.py new file mode 100755 index 000000000..182569a21 --- /dev/null +++ b/tests/performance/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" + +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "perftest.settings") + try: + from django.core.management import execute_from_command_line # noqa: PLC0415 + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/tests/performance/odm-data/flat_models/large_doc.json b/tests/performance/odm-data/flat_models/large_doc.json new file mode 100644 index 000000000..8a931b89d --- /dev/null +++ b/tests/performance/odm-data/flat_models/large_doc.json @@ -0,0 +1,252 @@ +{ + "field1": "kj9$mxz#p2qw8r*vn4@h7c&u1s", + "field2": "x3@9zf#mk7w$qp2n8v*r6j4h&c1u5s0a", + "field3": "p9#m2x$k7z@w3q8v*n4r&j6h1c5u0s", + "field4": "z8@x3m#k9w$p2q7v*r4n6j&h1c5u0s", + "field5": "m7#k9x$z3w@p8q2v*n4r6j&h1c5u0s", + "field6": "k6$x9m#z7w3p@q8v2n*r4j6h&c1u5s0", + "field7": "x5@m9k#z6w$p3q7v*n2r8j&h4c1u0s", + "field8": "m4#x8k$z9w6p@q3v*n7r2j&h5c1u0s", + "field9": "k3$m7x#z8w9p@q6v*n2r4j&h1c5u0s", + "field10": "x2@k6m#z7w8p$q9v*n3r1j&h4c5u0s", + "field11": "m1#x5k$z6w7p@q8v*n9r2j&h3c4u0s", + "field12": "k0$x4m#z5w6p@q7v*n8r1j&h2c3u9s", + "field13": "x9@m3k#z4w5p$q6v*n7r0j&h1c2u8s", + "field14": "m8#k2x$z3w4p@q5v*n6r9j&h0c1u7s", + "field15": "k7$x1m#z2w3p@q4v*n5r8j&h9c0u6s", + "field16": "x6@m0k#z1w2p$q3v*n4r7j&h8c9u5s", + "field17": "m5#x9k$z0w1p@q2v*n3r6j&h7c8u4s", + "field18": "k4$m8x#z9w0p@q1v*n2r5j&h6c7u3s", + "field19": "x3@k7m#z8w9p$q0v*n1r4j&h5c6u2s", + "field20": "m2#x6k$z7w8p@q9v*n0r3j&h4c5u1s", + "field21": "k1$x5m#z6w7p@q8v*n9r2j&h3c4u0s", + "field22": "x0@m4k#z5w6p$q7v*n8r1j&h2c3u9s", + "field23": "m9#k3x$z4w5p@q6v*n7r0j&h1c2u8s", + "field24": "k8$x2m#z3w4p@q5v*n6r9j&h0c1u7s", + "field25": "x7@m1k#z2w3p$q4v*n5r8j&h9c0u6s", + "field26": "m6#x0k$z1w2p@q3v*n4r7j&h8c9u5s", + "field27": "k5$m9x#z0w1p@q2v*n3r6j&h7c8u4s", + "field28": "x4@k8m#z9w0p$q1v*n2r5j&h6c7u3s", + "field29": "m3#x7k$z8w9p@q0v*n1r4j&h5c6u2s", + "field30": "k2$x6m#z7w8p@q9v*n0r3j&h4c5u1s", + "field31": "x1@m5k#z6w7p$q8v*n9r2j&h3c4u0s", + "field32": "m0#k4x$z5w6p@q7v*n8r1j&h2c3u9s", + "field33": "k9$x3m#z4w5p@q6v*n7r0j&h1c2u8s", + "field34": "x8@m2k#z3w4p$q5v*n6r9j&h0c1u7s", + "field35": "m7#x1k$z2w3p@q4v*n5r8j&h9c0u6s", + "field36": "k6$m0x#z1w2p@q3v*n4r7j&h8c9u5s", + "field37": "x5@k9m#z0w1p$q2v*n3r6j&h7c8u4s", + "field38": "m4#x8k$z9w0p@q1v*n2r5j&h6c7u3s", + "field39": "k3$x7m#z8w9p@q0v*n1r4j&h5c6u2s", + "field40": "x2@m6k#z7w8p$q9v*n0r3j&h4c5u1s", + "field41": "m1#k5x$z6w7p@q8v*n9r2j&h3c4u0s", + "field42": "k0$x4m#z5w6p@q7v*n8r1j&h2c3u9s", + "field43": "x9@m3k#z4w5p$q6v*n7r0j&h1c2u8s", + "field44": "m8#x2k$z3w4p@q5v*n6r9j&h0c1u7s", + "field45": "k7$m1x#z2w3p@q4v*n5r8j&h9c0u6s", + "field46": "x6@k0m#z1w2p$q3v*n4r7j&h8c9u5s", + "field47": "m5#x9k$z0w1p@q2v*n3r6j&h7c8u4s", + "field48": "k4$m8x#z9w0p@q1v*n2r5j&h6c7u3s", + "field49": "x3@k7m#z8w9p$q0v*n1r4j&h5c6u2s", + "field50": "m2#x6k$z7w8p@q9v*n0r3j&h4c5u1s", + "field51": "k1$x5m#z6w7p@q8v*n9r2j&h3c4u0s9f8d", + "field52": "x0@m4k#z5w6p$q7v*n8r1j&h2c3u9s7g6e", + "field53": "m9#k3x$z4w5p@q6v*n7r0j&h1c2u8s5b4a", + "field54": "k8$x2m#z3w4p@q5v*n6r9j&h0c1u7s3v2t", + "field55": "x7@m1k#z2w3p$q4v*n5r8j&h9c0u6s1y0w", + "field56": "m6#x0k$z1w2p@q3v*n4r7j&h8c9u5s9i8o", + "field57": "k5$m9x#z0w1p@q2v*n3r6j&h7c8u4s7l6p", + "field58": "x4@k8m#z9w0p$q1v*n2r5j&h6c7u3s5m4n", + "field59": "m3#x7k$z8w9p@q0v*n1r4j&h5c6u2s3q2r", + "field60": "k2$x6m#z7w8p@q9v*n0r3j&h4c5u1s1x0z", + "field61": "p7#q8r$s9t@u0v*w1x&y2z3a4b5c6d", + "field62": "f3$g4h#i5j@k6l*m7n&o8p9q0r1s2t", + "field63": "v5@w6x#y7z$a8b*c9d&e0f1g2h3i4j", + "field64": "n2#o3p$q4r@s5t*u6v&w7x8y9z0a1b", + "field65": "j8$k9l#m0n@o1p*q2r&s3t4u5v6w7x", + "field66": "b4@c5d#e6f$g7h*i8j&k9l0m1n2o3p", + "field67": "x6#y7z$a8b@c9d*e0f&g1h2i3j4k5l", + "field68": "t0$u1v#w2x@y3z*a4b&c5d6e7f8g9h", + "field69": "l9@m0n#o1p$q2r*s3t&u4v5w6x7y8z", + "field70": "h5#i6j$k7l@m8n*o9p&q0r1s2t3u4v", + "field71": "d1@e2f#g3h$i4j*k5l&m6n7o8p9q0r", + "field72": "z7$a8b#c9d@e0f*g1h&i2j3k4l5m6n", + "field73": "v3@w4x#y5z$a6b*c7d&e8f9g0h1i2j", + "field74": "r9#s0t$u1v@w2x*y3z&a4b5c6d7e8f", + "field75": "n5$o6p#q7r@s8t*u9v&w0x1y2z3a4b", + "field76": "j1@k2l#m3n$o4p*q5r&s6t7u8v9w0x", + "field77": "f7#g8h$i9j@k0l*m1n&o2p3q4r5s6t", + "field78": "b3@c4d#e5f$g6h*i7j&k8l9m0n1o2p", + "field79": "x9$y0z#a1b@c2d*e3f&g4h5i6j7k8l", + "field80": "t5#u6v$w7x@y8z*a9b&c0d1e2f3g4h", + "field81": "p1@q2r#s3t$u4v*w5x&y6z7a8b9c0d", + "field82": "l7#m8n$o9p@q0r*s1t&u2v3w4x5y6z", + "field83": "h3@i4j#k5l$m6n*o7p&q8r9s0t1u2v", + "field84": "d9$e0f#g1h@i2j*k3l&m4n5o6p7q8r", + "field85": "z5#a6b$c7d@e8f*g9h&i0j1k2l3m4n", + "field86": "v1@w2x#y3z$a4b*c5d&e6f7g8h9i0j", + "field87": "r7#s8t$u9v@w0x*y1z&a2b3c4d5e6f", + "field88": "n3@o4p#q5r$s6t*u7v&w8x9y0z1a2b", + "field89": "j9$k0l#m1n@o2p*q3r&s4t5u6v7w8x", + "field90": "f5#g6h$i7j@k8l*m9n&o0p1q2r3s4t", + "field91": "b1@c2d#e3f$g4h*i5j&k6l7m8n9o0p", + "field92": "x7#y8z$a9b@c0d*e1f&g2h3i4j5k6l", + "field93": "t3@u4v#w5x$y6z*a7b&c8d9e0f1g2h", + "field94": "p9$q0r#s1t@u2v*w3x&y4z5a6b7c8d", + "field95": "l5#m6n$o7p@q8r*s9t&u0v1w2x3y4z", + "field96": "h1@i2j#k3l$m4n*o5p&q6r7s8t9u0v", + "field97": "d7$e8f#g9h@i0j*k1l&m2n3o4p5q6r", + "field98": "z3#a4b$c5d@e6f*g7h&i8j9k0l1m2n", + "field99": "v9@w0x#y1z$a2b*c3d&e4f5g6h7i8j", + "field100": "r5#s6t$u7v@w8x*y9z&a0b1c2d3e4f", + "field101": "n1@o2p#q3r$s4t*u5v&w6x7y8z9a0b", + "field102": "j7$k8l#m9n@o0p*q1r&s2t3u4v5w6x", + "field103": "f3#g4h$i5j@k6l*m7n&o8p9q0r1s2t", + "field104": "b9@c0d#e1f$g2h*i3j&k4l5m6n7o8p", + "field105": "x5$y6z#a7b@c8d*e9f&g0h1i2j3k4l", + "field106": "t1#u2v$w3x@y4z*a5b&c6d7e8f9g0h", + "field107": "p7@q8r#s9t$u0v*w1x&y2z3a4b5c6d", + "field108": "l3#m4n$o5p@q6r*s7t&u8v9w0x1y2z", + "field109": "h9$i0j#k1l@m2n*o3p&q4r5s6t7u8v", + "field110": "d5@e6f#g7h$i8j*k9l&m0n1o2p3q4r", + "field111": "z1#a2b$c3d@e4f*g5h&i6j7k8l9m0n", + "field112": "v7$w8x#y9z@a0b*c1d&e2f3g4h5i6j", + "field113": "r3@s4t#u5v$w6x*y7z&a8b9c0d1e2f", + "field114": "n9#o0p$q1r@s2t*u3v&w4x5y6z7a8b", + "field115": "j5$k6l#m7n@o8p*q9r&s0t1u2v3w4x", + "field116": "f1@g2h#i3j$k4l*m5n&o6p7q8r9s0t", + "field117": "b7#c8d$e9f@g0h*i1j&k2l3m4n5o6p", + "field118": "x3@y4z#a5b$c6d*e7f&g8h9i0j1k2l", + "field119": "t9$u0v#w1x@y2z*a3b&c4d5e6f7g8h", + "field120": "p5#q6r$s7t@u8v*w9x&y0z1a2b3c4d", + "field121": "l1@m2n#o3p$q4r*s5t&u6v7w8x9y0z", + "field122": "h7$i8j#k9l@m0n*o1p&q2r3s4t5u6v", + "field123": "d3@e4f#g5h$i6j*k7l&m8n9o0p1q2r", + "field124": "z9#a0b$c1d@e2f*g3h&i4j5k6l7m8n", + "field125": "v5$w6x#y7z@a8b*c9d&e0f1g2h3i4j", + "field126": 42, + "field127": 789, + "field128": 156, + "field129": 923, + "field130": 347, + "field131": 681, + "field132": 294, + "field133": 835, + "field134": 167, + "field135": 459, + "field136": 672, + "field137": 381, + "field138": 928, + "field139": 514, + "field140": 760, + "field141": 293, + "field142": 846, + "field143": 107, + "field144": 582, + "field145": 734, + "field146": 395, + "field147": 861, + "field148": 248, + "field149": 657, + "field150": 419, + "field151": 703, + "field152": 186, + "field153": 542, + "field154": 698, + "field155": 375, + "field156": 829, + "field157": 164, + "field158": 517, + "field159": 743, + "field160": 286, + "field161": 904, + "field162": 358, + "field163": 621, + "field164": 795, + "field165": 432, + "field166": 876, + "field167": 189, + "field168": 653, + "field169": 407, + "field170": 728, + "field171": 564, + "field172": 391, + "field173": 817, + "field174": 275, + "field175": 938, + "field176": 102, + "field177": 586, + "field178": 749, + "field179": 423, + "field180": 867, + "field181": 234, + "field182": 695, + "field183": 458, + "field184": 701, + "field185": 316, + "field186": 872, + "field187": 539, + "field188": 684, + "field189": 297, + "field190": 815, + "field191": 463, + "field192": 728, + "field193": 156, + "field194": 934, + "field195": 507, + "field196": 682, + "field197": 349, + "field198": 876, + "field199": 214, + "field200": 758, + "field201": 436, + "field202": 691, + "field203": 325, + "field204": 809, + "field205": 582, + "field206": 147, + "field207": 763, + "field208": 498, + "field209": 625, + "field210": 384, + "field211": 917, + "field212": 256, + "field213": 743, + "field214": 469, + "field215": 836, + "field216": 172, + "field217": 594, + "field218": 721, + "field219": 358, + "field220": 907, + "field221": 283, + "field222": 649, + "field223": 416, + "field224": 785, + "field225": 532, + "field226": 698, + "field227": 245, + "field228": 871, + "field229": 409, + "field230": 756, + "field231": 183, + "field232": 627, + "field233": 394, + "field234": 819, + "field235": 568, + "field236": 742, + "field237": 125, + "field238": 896, + "field239": 453, + "field240": 607, + "field241": 784, + "field242": 329, + "field243": 856, + "field244": 291, + "field245": 673, + "field246": 418, + "field247": 905, + "field248": 562, + "field249": 739, + "field250": 864 +} \ No newline at end of file diff --git a/tests/performance/odm-data/flat_models/small_doc.json b/tests/performance/odm-data/flat_models/small_doc.json new file mode 100644 index 000000000..d0727271f --- /dev/null +++ b/tests/performance/odm-data/flat_models/small_doc.json @@ -0,0 +1 @@ +{"field1":"miNVpaKW","field2":"CS5VwrwN","field3":"Oq5Csk1w","field4":"ZPm57dhu","field5":"gxUpzIjg","field6":"Smo9whci","field7":"TW34kfzq","field8":55336395,"field9":41992681,"field10":72188733,"field11":46660880,"field12":3527055,"field13":74094448} \ No newline at end of file diff --git a/tests/performance/odm-data/nested_models/large_doc_nested.json b/tests/performance/odm-data/nested_models/large_doc_nested.json new file mode 100644 index 000000000..497a6e255 --- /dev/null +++ b/tests/performance/odm-data/nested_models/large_doc_nested.json @@ -0,0 +1,242 @@ +{ + "embedded_str_doc_1": { + "field1": "kj9$mxz#p2qw8r*vn4@h7c&u1s", + "field2": "x3@9zf#mk7w$qp2n8v*r6j4h&c1u5s0a", + "field3": "p9#m2x$k7z@w3q8v*n4r&j6h1c5u0s", + "field4": "z8@x3m#k9w$p2q7v*r4n6j&h1c5u0s", + "field5": "m7#k9x$z3w@p8q2v*n4r6j&h1c5u0s", + "field6": "k6$x9m#z7w3p@q8v2n*r4j6h&c1u5s0", + "field7": "x5@m9k#z6w$p3q7v*n2r8j&h4c1u0s", + "field8": "m4#x8k$z9w6p@q3v*n7r2j&h5c1u0s", + "field9": "k3$m7x#z8w9p@q6v*n2r4j&h1c5u0s", + "field10": "x2@k6m#z7w8p$q9v*n3r1j&h4c5u0s", + "field11": "m1#x5k$z6w7p@q8v*n9r2j&h3c4u0s", + "field12": "k0$x4m#z5w6p@q7v*n8r1j&h2c3u9s", + "field13": "x9@m3k#z4w5p$q6v*n7r0j&h1c2u8s", + "field14": "m8#k2x$z3w4p@q5v*n6r9j&h0c1u7s", + "field15": "k7$x1m#z2w3p@q4v*n5r8j&h9c0u6s" + }, + "embedded_str_doc_2": { + "field1": "x6@m0k#z1w2p$q3v*n4r7j&h8c9u5s", + "field2": "m5#x9k$z0w1p@q2v*n3r6j&h7c8u4s", + "field3": "k4$m8x#z9w0p@q1v*n2r5j&h6c7u3s", + "field4": "x3@k7m#z8w9p$q0v*n1r4j&h5c6u2s", + "field5": "m2#x6k$z7w8p@q9v*n0r3j&h4c5u1s", + "field6": "k1$x5m#z6w7p@q8v*n9r2j&h3c4u0s", + "field7": "x0@m4k#z5w6p$q7v*n8r1j&h2c3u9s", + "field8": "m9#k3x$z4w5p@q6v*n7r0j&h1c2u8s", + "field9": "k8$x2m#z3w4p@q5v*n6r9j&h0c1u7s", + "field10": "x7@m1k#z2w3p$q4v*n5r8j&h9c0u6s", + "field11": "m6#x0k$z1w2p@q3v*n4r7j&h8c9u5s", + "field12": "k5$m9x#z0w1p@q2v*n3r6j&h7c8u4s", + "field13": "x4@k8m#z9w0p$q1v*n2r5j&h6c7u3s", + "field14": "m3#x7k$z8w9p@q0v*n1r4j&h5c6u2s", + "field15": "k2$x6m#z7w8p@q9v*n0r3j&h4c5u1s" + }, + "embedded_str_doc_3": { + "field1": "k9$x3m#z4w5p@q6v*n7r0j&h1c2u8s", + "field2": "x8@m2k#z3w4p$q5v*n6r9j&h0c1u7s", + "field3": "m7#x1k$z2w3p@q4v*n5r8j&h9c0u6s", + "field4": "k6$m0x#z1w2p@q3v*n4r7j&h8c9u5s", + "field5": "x5@k9m#z0w1p$q2v*n3r6j&h7c8u4s", + "field6": "m4#x8k$z9w0p@q1v*n2r5j&h6c7u3s", + "field7": "k3$x7m#z8w9p@q0v*n1r4j&h5c6u2s", + "field8": "x2@m6k#z7w8p$q9v*n0r3j&h4c5u1s", + "field9": "m1#k5x$z6w7p@q8v*n9r2j&h3c4u0s", + "field10": "k0$x4m#z5w6p@q7v*n8r1j&h2c3u9s", + "field11": "x9@m3k#z4w5p$q6v*n7r0j&h1c2u8s", + "field12": "m8#x2k$z3w4p@q5v*n6r9j&h0c1u7s", + "field13": "k7$m1x#z2w3p@q4v*n5r8j&h9c0u6s", + "field14": "x6@k0m#z1w2p$q3v*n4r7j&h8c9u5s", + "field15": "m5#x9k$z0w1p@q2v*n3r6j&h7c8u4s" + }, + "embedded_str_doc_4": { + "field1": "k1$x5m#z6w7p@q8v*n9r2j&h3c4u0s9f8d", + "field2": "x0@m4k#z5w6p$q7v*n8r1j&h2c3u9s7g6e", + "field3": "m9#k3x$z4w5p@q6v*n7r0j&h1c2u8s5b4a", + "field4": "k8$x2m#z3w4p@q5v*n6r9j&h0c1u7s3v2t", + "field5": "x7@m1k#z2w3p$q4v*n5r8j&h9c0u6s1y0w", + "field6": "m6#x0k$z1w2p@q3v*n4r7j&h8c9u5s9i8o", + "field7": "k5$m9x#z0w1p@q2v*n3r6j&h7c8u4s7l6p", + "field8": "x4@k8m#z9w0p$q1v*n2r5j&h6c7u3s5m4n", + "field9": "m3#x7k$z8w9p@q0v*n1r4j&h5c6u2s3q2r", + "field10": "k2$x6m#z7w8p@q9v*n0r3j&h4c5u1s1x0z", + "field11": "p7#q8r$s9t@u0v*w1x&y2z3a4b5c6d", + "field12": "f3$g4h#i5j@k6l*m7n&o8p9q0r1s2t", + "field13": "v5@w6x#y7z$a8b*c9d&e0f1g2h3i4j", + "field14": "n2#o3p$q4r@s5t*u6v&w7x8y9z0a1b", + "field15": "j8$k9l#m0n@o1p*q2r&s3t4u5v6w7x" + }, + "embedded_str_doc_5": { + "field1": "d1@e2f#g3h$i4j*k5l&m6n7o8p9q0r", + "field2": "z7$a8b#c9d@e0f*g1h&i2j3k4l5m6n", + "field3": "v3@w4x#y5z$a6b*c7d&e8f9g0h1i2j", + "field4": "r9#s0t$u1v@w2x*y3z&a4b5c6d7e8f", + "field5": "n5$o6p#q7r@s8t*u9v&w0x1y2z3a4b", + "field6": "j1@k2l#m3n$o4p*q5r&s6t7u8v9w0x", + "field7": "f7#g8h$i9j@k0l*m1n&o2p3q4r5s6t", + "field8": "b3@c4d#e5f$g6h*i7j&k8l9m0n1o2p", + "field9": "x9$y0z#a1b@c2d*e3f&g4h5i6j7k8l", + "field10": "t5#u6v$w7x@y8z*a9b&c0d1e2f3g4h", + "field11": "p1@q2r#s3t$u4v*w5x&y6z7a8b9c0d", + "field12": "l7#m8n$o9p@q0r*s1t&u2v3w4x5y6z", + "field13": "h3@i4j#k5l$m6n*o7p&q8r9s0t1u2v", + "field14": "d9$e0f#g1h@i2j*k3l&m4n5o6p7q8r", + "field15": "z5#a6b$c7d@e8f*g9h&i0j1k2l3m4n" + }, + "embedded_str_doc_array": [ + { + "field1": "n3@o4p#q5r$s6t*u7v&w8x9y0z1a2b", + "field2": "j9$k0l#m1n@o2p*q3r&s4t5u6v7w8x", + "field3": "f5#g6h$i7j@k8l*m9n&o0p1q2r3s4t", + "field4": "b1@c2d#e3f$g4h*i5j&k6l7m8n9o0p", + "field5": "x7#y8z$a9b@c0d*e1f&g2h3i4j5k6l", + "field6": "t3@u4v#w5x$y6z*a7b&c8d9e0f1g2h", + "field7": "p9$q0r#s1t@u2v*w3x&y4z5a6b7c8d", + "field8": "l5#m6n$o7p@q8r*s9t&u0v1w2x3y4z", + "field9": "h1@i2j#k3l$m4n*o5p&q6r7s8t9u0v", + "field10": "d7$e8f#g9h@i0j*k1l&m2n3o4p5q6r", + "field11": "z3#a4b$c5d@e6f*g7h&i8j9k0l1m2n", + "field12": "v9@w0x#y1z$a2b*c3d&e4f5g6h7i8j", + "field13": "r5#s6t$u7v@w8x*y9z&a0b1c2d3e4f", + "field14": "n1@o2p#q3r$s4t*u5v&w6x7y8z9a0b", + "field15": "j7$k8l#m9n@o0p*q1r&s2t3u4v5w6x" + }, + { + "field1": "t1#u2v$w3x@y4z*a5b&c6d7e8f9g0h", + "field2": "p7@q8r#s9t$u0v*w1x&y2z3a4b5c6d", + "field3": "l3#m4n$o5p@q6r*s7t&u8v9w0x1y2z", + "field4": "h9$i0j#k1l@m2n*o3p&q4r5s6t7u8v", + "field5": "d5@e6f#g7h$i8j*k9l&m0n1o2p3q4r", + "field6": "z1#a2b$c3d@e4f*g5h&i6j7k8l9m0n", + "field7": "v7$w8x#y9z@a0b*c1d&e2f3g4h5i6j", + "field8": "r3@s4t#u5v$w6x*y7z&a8b9c0d1e2f", + "field9": "n9#o0p$q1r@s2t*u3v&w4x5y6z7a8b", + "field10": "j5$k6l#m7n@o8p*q9r&s0t1u2v3w4x", + "field11": "f1@g2h#i3j$k4l*m5n&o6p7q8r9s0t", + "field12": "b7#c8d$e9f@g0h*i1j&k2l3m4n5o6p", + "field13": "x3@y4z#a5b$c6d*e7f&g8h9i0j1k2l", + "field14": "t9$u0v#w1x@y2z*a3b&c4d5e6f7g8h", + "field15": "p5#q6r$s7t@u8v*w9x&y0z1a2b3c4d" + } + ], + "embedded_int_doc_8": { + "field1": 42, + "field2": 789, + "field3": 156, + "field4": 923, + "field5": 347, + "field6": 681, + "field7": 294, + "field8": 835, + "field9": 167, + "field10": 459, + "field11": 672, + "field12": 381, + "field13": 928, + "field14": 514, + "field15": 760 + }, + "embedded_int_doc_9": { + "field1": 846, + "field2": 107, + "field3": 582, + "field4": 734, + "field5": 395, + "field6": 861, + "field7": 248, + "field8": 657, + "field9": 419, + "field10": 703, + "field11": 186, + "field12": 542, + "field13": 698, + "field14": 375, + "field15": 829 + }, + "embedded_int_doc_10": { + "field1": 904, + "field2": 358, + "field3": 621, + "field4": 795, + "field5": 432, + "field6": 876, + "field7": 189, + "field8": 653, + "field9": 407, + "field10": 728, + "field11": 564, + "field12": 391, + "field13": 817, + "field14": 275, + "field15": 938 + }, + "embedded_int_doc_11": { + "field1": 867, + "field2": 234, + "field3": 695, + "field4": 458, + "field5": 701, + "field6": 316, + "field7": 872, + "field8": 539, + "field9": 684, + "field10": 297, + "field11": 815, + "field12": 463, + "field13": 728, + "field14": 156, + "field15": 934 + }, + "embedded_int_doc_12": { + "field1": 214, + "field2": 758, + "field3": 436, + "field4": 691, + "field5": 325, + "field6": 809, + "field7": 582, + "field8": 147, + "field9": 763, + "field10": 498, + "field11": 625, + "field12": 384, + "field13": 917, + "field14": 256, + "field15": 743 + }, + "embedded_int_doc_13": { + "field1": 721, + "field2": 358, + "field3": 907, + "field4": 283, + "field5": 649, + "field6": 416, + "field7": 785, + "field8": 532, + "field9": 698, + "field10": 245, + "field11": 871, + "field12": 409, + "field13": 756, + "field14": 183, + "field15": 627 + }, + "embedded_int_doc_14": { + "field1": 896, + "field2": 453, + "field3": 607, + "field4": 784, + "field5": 329, + "field6": 856, + "field7": 291, + "field8": 673, + "field9": 418, + "field10": 905, + "field11": 562, + "field12": 739, + "field13": 864, + "field14": 285, + "field15": 928 + } +} diff --git a/tests/performance/perftest/__init__.py b/tests/performance/perftest/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/performance/perftest/models.py b/tests/performance/perftest/models.py new file mode 100644 index 000000000..ee7707d92 --- /dev/null +++ b/tests/performance/perftest/models.py @@ -0,0 +1,348 @@ +from django.db import models + +from django_mongodb_backend.fields import EmbeddedModelArrayField, EmbeddedModelField, ObjectIdField +from django_mongodb_backend.models import EmbeddedModel + + +class SmallFlatModel(models.Model): + field1 = models.CharField(max_length=100) + field2 = models.CharField(max_length=100) + field3 = models.CharField(max_length=100) + field4 = models.CharField(max_length=100) + field5 = models.CharField(max_length=100) + field6 = models.CharField(max_length=100) + field7 = models.CharField(max_length=100) + field8 = models.IntegerField() + field9 = models.IntegerField() + field10 = models.IntegerField() + field11 = models.IntegerField() + field12 = models.IntegerField() + field13 = models.IntegerField() + + +class ForeignKeyModel(models.Model): + name = models.CharField(max_length=100) + + +class SmallFlatModelFk(models.Model): + field1 = models.CharField(max_length=100) + field2 = models.CharField(max_length=100) + field3 = models.CharField(max_length=100) + field4 = models.CharField(max_length=100) + field5 = models.CharField(max_length=100) + field6 = models.CharField(max_length=100) + field7 = models.CharField(max_length=100) + field8 = models.IntegerField() + field9 = models.IntegerField() + field10 = models.IntegerField() + field11 = models.IntegerField() + field12 = models.IntegerField() + field13 = models.IntegerField() + field_fk = models.ForeignKey(ForeignKeyModel, on_delete=models.DO_NOTHING) + + +class LargeFlatModel(models.Model): + field1 = models.CharField(max_length=100) + field2 = models.CharField(max_length=100) + field3 = models.CharField(max_length=100) + field4 = models.CharField(max_length=100) + field5 = models.CharField(max_length=100) + field6 = models.CharField(max_length=100) + field7 = models.CharField(max_length=100) + field8 = models.CharField(max_length=100) + field9 = models.CharField(max_length=100) + field10 = models.CharField(max_length=100) + field11 = models.CharField(max_length=100) + field12 = models.CharField(max_length=100) + field13 = models.CharField(max_length=100) + field14 = models.CharField(max_length=100) + field15 = models.CharField(max_length=100) + field16 = models.CharField(max_length=100) + field17 = models.CharField(max_length=100) + field18 = models.CharField(max_length=100) + field19 = models.CharField(max_length=100) + field20 = models.CharField(max_length=100) + field21 = models.CharField(max_length=100) + field22 = models.CharField(max_length=100) + field23 = models.CharField(max_length=100) + field24 = models.CharField(max_length=100) + field25 = models.CharField(max_length=100) + field26 = models.CharField(max_length=100) + field27 = models.CharField(max_length=100) + field28 = models.CharField(max_length=100) + field29 = models.CharField(max_length=100) + field30 = models.CharField(max_length=100) + field31 = models.CharField(max_length=100) + field32 = models.CharField(max_length=100) + field33 = models.CharField(max_length=100) + field34 = models.CharField(max_length=100) + field35 = models.CharField(max_length=100) + field36 = models.CharField(max_length=100) + field37 = models.CharField(max_length=100) + field38 = models.CharField(max_length=100) + field39 = models.CharField(max_length=100) + field40 = models.CharField(max_length=100) + field41 = models.CharField(max_length=100) + field42 = models.CharField(max_length=100) + field43 = models.CharField(max_length=100) + field44 = models.CharField(max_length=100) + field45 = models.CharField(max_length=100) + field46 = models.CharField(max_length=100) + field47 = models.CharField(max_length=100) + field48 = models.CharField(max_length=100) + field49 = models.CharField(max_length=100) + field50 = models.CharField(max_length=100) + field51 = models.CharField(max_length=100) + field52 = models.CharField(max_length=100) + field53 = models.CharField(max_length=100) + field54 = models.CharField(max_length=100) + field55 = models.CharField(max_length=100) + field56 = models.CharField(max_length=100) + field57 = models.CharField(max_length=100) + field58 = models.CharField(max_length=100) + field59 = models.CharField(max_length=100) + field60 = models.CharField(max_length=100) + field61 = models.CharField(max_length=100) + field62 = models.CharField(max_length=100) + field63 = models.CharField(max_length=100) + field64 = models.CharField(max_length=100) + field65 = models.CharField(max_length=100) + field66 = models.CharField(max_length=100) + field67 = models.CharField(max_length=100) + field68 = models.CharField(max_length=100) + field69 = models.CharField(max_length=100) + field70 = models.CharField(max_length=100) + field71 = models.CharField(max_length=100) + field72 = models.CharField(max_length=100) + field73 = models.CharField(max_length=100) + field74 = models.CharField(max_length=100) + field75 = models.CharField(max_length=100) + field76 = models.CharField(max_length=100) + field77 = models.CharField(max_length=100) + field78 = models.CharField(max_length=100) + field79 = models.CharField(max_length=100) + field80 = models.CharField(max_length=100) + field81 = models.CharField(max_length=100) + field82 = models.CharField(max_length=100) + field83 = models.CharField(max_length=100) + field84 = models.CharField(max_length=100) + field85 = models.CharField(max_length=100) + field86 = models.CharField(max_length=100) + field87 = models.CharField(max_length=100) + field88 = models.CharField(max_length=100) + field89 = models.CharField(max_length=100) + field90 = models.CharField(max_length=100) + field91 = models.CharField(max_length=100) + field92 = models.CharField(max_length=100) + field93 = models.CharField(max_length=100) + field94 = models.CharField(max_length=100) + field95 = models.CharField(max_length=100) + field96 = models.CharField(max_length=100) + field97 = models.CharField(max_length=100) + field98 = models.CharField(max_length=100) + field99 = models.CharField(max_length=100) + field100 = models.CharField(max_length=100) + field101 = models.CharField(max_length=100) + field102 = models.CharField(max_length=100) + field103 = models.CharField(max_length=100) + field104 = models.CharField(max_length=100) + field105 = models.CharField(max_length=100) + field106 = models.CharField(max_length=100) + field107 = models.CharField(max_length=100) + field108 = models.CharField(max_length=100) + field109 = models.CharField(max_length=100) + field110 = models.CharField(max_length=100) + field111 = models.CharField(max_length=100) + field112 = models.CharField(max_length=100) + field113 = models.CharField(max_length=100) + field114 = models.CharField(max_length=100) + field115 = models.CharField(max_length=100) + field116 = models.CharField(max_length=100) + field117 = models.CharField(max_length=100) + field118 = models.CharField(max_length=100) + field119 = models.CharField(max_length=100) + field120 = models.CharField(max_length=100) + field121 = models.CharField(max_length=100) + field122 = models.CharField(max_length=100) + field123 = models.CharField(max_length=100) + field124 = models.CharField(max_length=100) + field125 = models.CharField(max_length=100) + field126 = models.IntegerField() + field127 = models.IntegerField() + field128 = models.IntegerField() + field129 = models.IntegerField() + field130 = models.IntegerField() + field131 = models.IntegerField() + field132 = models.IntegerField() + field133 = models.IntegerField() + field134 = models.IntegerField() + field135 = models.IntegerField() + field136 = models.IntegerField() + field137 = models.IntegerField() + field138 = models.IntegerField() + field139 = models.IntegerField() + field140 = models.IntegerField() + field141 = models.IntegerField() + field142 = models.IntegerField() + field143 = models.IntegerField() + field144 = models.IntegerField() + field145 = models.IntegerField() + field146 = models.IntegerField() + field147 = models.IntegerField() + field148 = models.IntegerField() + field149 = models.IntegerField() + field150 = models.IntegerField() + field151 = models.IntegerField() + field152 = models.IntegerField() + field153 = models.IntegerField() + field154 = models.IntegerField() + field155 = models.IntegerField() + field156 = models.IntegerField() + field157 = models.IntegerField() + field158 = models.IntegerField() + field159 = models.IntegerField() + field160 = models.IntegerField() + field161 = models.IntegerField() + field162 = models.IntegerField() + field163 = models.IntegerField() + field164 = models.IntegerField() + field165 = models.IntegerField() + field166 = models.IntegerField() + field167 = models.IntegerField() + field168 = models.IntegerField() + field169 = models.IntegerField() + field170 = models.IntegerField() + field171 = models.IntegerField() + field172 = models.IntegerField() + field173 = models.IntegerField() + field174 = models.IntegerField() + field175 = models.IntegerField() + field176 = models.IntegerField() + field177 = models.IntegerField() + field178 = models.IntegerField() + field179 = models.IntegerField() + field180 = models.IntegerField() + field181 = models.IntegerField() + field182 = models.IntegerField() + field183 = models.IntegerField() + field184 = models.IntegerField() + field185 = models.IntegerField() + field186 = models.IntegerField() + field187 = models.IntegerField() + field188 = models.IntegerField() + field189 = models.IntegerField() + field190 = models.IntegerField() + field191 = models.IntegerField() + field192 = models.IntegerField() + field193 = models.IntegerField() + field194 = models.IntegerField() + field195 = models.IntegerField() + field196 = models.IntegerField() + field197 = models.IntegerField() + field198 = models.IntegerField() + field199 = models.IntegerField() + field200 = models.IntegerField() + field201 = models.IntegerField() + field202 = models.IntegerField() + field203 = models.IntegerField() + field204 = models.IntegerField() + field205 = models.IntegerField() + field206 = models.IntegerField() + field207 = models.IntegerField() + field208 = models.IntegerField() + field209 = models.IntegerField() + field210 = models.IntegerField() + field211 = models.IntegerField() + field212 = models.IntegerField() + field213 = models.IntegerField() + field214 = models.IntegerField() + field215 = models.IntegerField() + field216 = models.IntegerField() + field217 = models.IntegerField() + field218 = models.IntegerField() + field219 = models.IntegerField() + field220 = models.IntegerField() + field221 = models.IntegerField() + field222 = models.IntegerField() + field223 = models.IntegerField() + field224 = models.IntegerField() + field225 = models.IntegerField() + field226 = models.IntegerField() + field227 = models.IntegerField() + field228 = models.IntegerField() + field229 = models.IntegerField() + field230 = models.IntegerField() + field231 = models.IntegerField() + field232 = models.IntegerField() + field233 = models.IntegerField() + field234 = models.IntegerField() + field235 = models.IntegerField() + field236 = models.IntegerField() + field237 = models.IntegerField() + field238 = models.IntegerField() + field239 = models.IntegerField() + field240 = models.IntegerField() + field241 = models.IntegerField() + field242 = models.IntegerField() + field243 = models.IntegerField() + field244 = models.IntegerField() + field245 = models.IntegerField() + field246 = models.IntegerField() + field247 = models.IntegerField() + field248 = models.IntegerField() + field249 = models.IntegerField() + field250 = models.IntegerField() + + +class StringEmbeddedModel(EmbeddedModel): + unique_id = ObjectIdField() + field1 = models.CharField(max_length=100) + field2 = models.CharField(max_length=100) + field3 = models.CharField(max_length=100) + field4 = models.CharField(max_length=100) + field5 = models.CharField(max_length=100) + field6 = models.CharField(max_length=100) + field7 = models.CharField(max_length=100) + field8 = models.CharField(max_length=100) + field9 = models.CharField(max_length=100) + field10 = models.CharField(max_length=100) + field11 = models.CharField(max_length=100) + field12 = models.CharField(max_length=100) + field13 = models.CharField(max_length=100) + field14 = models.CharField(max_length=100) + field15 = models.CharField(max_length=100) + + +class IntegerEmbeddedModel(EmbeddedModel): + unique_id = ObjectIdField() + field1 = models.IntegerField() + field2 = models.IntegerField() + field3 = models.IntegerField() + field4 = models.IntegerField() + field5 = models.IntegerField() + field6 = models.IntegerField() + field7 = models.IntegerField() + field8 = models.IntegerField() + field9 = models.IntegerField() + field10 = models.IntegerField() + field11 = models.IntegerField() + field12 = models.IntegerField() + field13 = models.IntegerField() + field14 = models.IntegerField() + field15 = models.IntegerField() + + +class LargeNestedModel(models.Model): + embedded_str_doc_1 = EmbeddedModelField(StringEmbeddedModel) + embedded_str_doc_2 = EmbeddedModelField(StringEmbeddedModel) + embedded_str_doc_3 = EmbeddedModelField(StringEmbeddedModel) + embedded_str_doc_4 = EmbeddedModelField(StringEmbeddedModel) + embedded_str_doc_5 = EmbeddedModelField(StringEmbeddedModel) + embedded_str_doc_array = EmbeddedModelArrayField(StringEmbeddedModel) + embedded_int_doc_8 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_9 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_10 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_11 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_12 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_13 = EmbeddedModelField(IntegerEmbeddedModel) + embedded_int_doc_14 = EmbeddedModelField(IntegerEmbeddedModel) diff --git a/tests/performance/perftest/settings.py b/tests/performance/perftest/settings.py new file mode 100644 index 000000000..3ceaec144 --- /dev/null +++ b/tests/performance/perftest/settings.py @@ -0,0 +1,107 @@ +""" +Django settings for perftest project. + +Generated by 'django-admin startproject' using Django 5.2.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-xle!u*(htb-zn^-*kap_3s5u1#sm8p#f%@j@-6j6+97%p*n_ho" # noqa: S105 + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ["perftest"] + +MIDDLEWARE = [] + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "perftest.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django_mongodb_backend", + "HOST": "mongodb://localhost:27017", + "NAME": "benchmarking", + "PORT": 27017, + }, +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField" diff --git a/tests/performance/perftest/tests.py b/tests/performance/perftest/tests.py new file mode 100644 index 000000000..c0b00a183 --- /dev/null +++ b/tests/performance/perftest/tests.py @@ -0,0 +1,378 @@ +"""Tests for the MongoDB ODM Performance Benchmark Spec. + +See https://github.com/mongodb/specifications/blob/master/source/benchmarking/odm-benchmarking.md + + +To set up the benchmarks locally:: + git clone --depth 1 https://github.com/mongodb/specifications.git + pushd specifications/source/benchmarking/odm-data + tar xf flat_models.tgz + tar xf nested_models.tgz + popd + export TEST_PATH="specifications/source/benchmarking/odm-data" + export OUTPUT_FILE="results.json" + +Then to run all benchmarks quickly:: + cd tests/performance + FASTBENCH=1 python manage.py test + +To run individual benchmarks quickly:: + cd tests/performance + FASTBENCH=1 python manage.py test perftest.tests.TestLargeNestedDocFilterArray +""" + +import json +import os +import time +import warnings +from pathlib import Path + +from bson import ObjectId, encode +from django.test import ( + TestCase, +) + +from .models import ( + ForeignKeyModel, + IntegerEmbeddedModel, + LargeFlatModel, + LargeNestedModel, + SmallFlatModel, + SmallFlatModelFk, + StringEmbeddedModel, +) + +OUTPUT_FILE = os.environ.get("OUTPUT_FILE") + +if os.environ.get("FASTBENCH"): + NUM_ITERATIONS = 1 + MIN_ITERATION_TIME = 5 + MAX_ITERATION_TIME = 10 + NUM_DOCS = 1000 +else: + NUM_ITERATIONS = 10 + MIN_ITERATION_TIME = 30 + MAX_ITERATION_TIME = 120 + NUM_DOCS = 10000 + +TEST_PATH = os.environ.get("TEST_PATH", Path(os.path.realpath(__file__)).parent.parent / "odm-data") + +result_data: list = [] + + +def tearDownModule(): + output = json.dumps(result_data, indent=4) + if OUTPUT_FILE: + with open(OUTPUT_FILE, "w") as opf: # noqa: PTH123 + opf.write(output) + else: + print(output) # noqa: T201 + + +class Timer: + def __enter__(self): + self.start = time.monotonic() + return self + + def __exit__(self, *args): + self.end = time.monotonic() + self.interval = self.end - self.start + + +# Copied from the driver benchmarking suite. +class PerformanceTest: + dataset: str + data_size: int + + def setUp(self): + self.setup_time = time.monotonic() + + def tearDown(self): + duration = time.monotonic() - self.setup_time + # Remove "Test" so that TestMyTestName is reported as "MyTestName". + name = self.__class__.__name__[4:] + median = self.percentile(50) + megabytes_per_sec = self.data_size / median / 1000000 + print( # noqa: T201 + f"Completed {self.__class__.__name__} {megabytes_per_sec:.3f} MB/s, " + f"MEDIAN={self.percentile(50):.3f}s, " + f"total time={duration:.3f}s, iterations={len(self.results)}" + ) + result_data.append( + { + "info": { + "test_name": name, + }, + "metrics": [ + { + "name": "megabytes_per_sec", + "type": "MEDIAN", + "value": megabytes_per_sec, + "metadata": { + "improvement_direction": "up", + "measurement_unit": "megabytes_per_second", + }, + }, + ], + } + ) + + def before(self): + pass + + def do_task(self): + raise NotImplementedError + + def after(self): + pass + + def percentile(self, percentile): + if hasattr(self, "results"): + sorted_results = sorted(self.results) + percentile_index = int(len(sorted_results) * percentile / 100) - 1 + return sorted_results[percentile_index] + self.fail("Test execution failed") + return None + + def runTest(self): + results = [] + start = time.monotonic() + i = 0 + while True: + i += 1 + self.before() + with Timer() as timer: + self.do_task() + self.after() + results.append(timer.interval) + duration = time.monotonic() - start + if duration > MIN_ITERATION_TIME and i >= NUM_ITERATIONS: + break + if duration > MAX_ITERATION_TIME: + with warnings.catch_warnings(): + warnings.simplefilter("default") + warnings.warn( + f"{self.__class__.__name__} timed out after {MAX_ITERATION_TIME}s, " + f"completed {i}/{NUM_ITERATIONS} iterations.", + stacklevel=2, + ) + + break + + self.results = results + + +class SmallFlatDocTest(PerformanceTest): + dataset = "small_doc.json" + + def setUp(self): + super().setUp() + with open(Path(TEST_PATH) / Path("flat_models") / self.dataset) as data: # noqa: PTH123 + self.document = json.load(data) + + self.data_size = len(encode(self.document)) * NUM_DOCS + self.documents = [self.document.copy() for _ in range(NUM_DOCS)] + + +class TestSmallFlatDocCreation(SmallFlatDocTest, TestCase): + def do_task(self): + for doc in self.documents: + SmallFlatModel.objects.create(**doc) + + def after(self): + SmallFlatModel.objects.all().delete() + + +class TestSmallFlatDocUpdate(SmallFlatDocTest, TestCase): + def setUp(self): + super().setUp() + self.models = [] + for doc in self.documents: + self.models.append(SmallFlatModel(**doc)) + SmallFlatModel.objects.bulk_create(self.models) + self.data_size = len(encode({"field1": "updated_value0"})) * NUM_DOCS + self.iteration = 0 + + def do_task(self): + for model in self.models: + model.field1 = "updated_value" + str(self.iteration) + model.save() + self.iteration += 1 + + def tearDown(self): + SmallFlatModel.objects.all().delete() + + +class TestSmallFlatDocFilterById(SmallFlatDocTest, TestCase): + def setUp(self): + super().setUp() + self.ids = [] + models = [] + for doc in self.documents: + models.append(SmallFlatModel(**doc)) + inserted = SmallFlatModel.objects.bulk_create(models) + self.ids = [model.id for model in inserted] + + def do_task(self): + for _id in self.ids: + list(SmallFlatModel.objects.filter(id=_id)) + + def tearDown(self): + super().tearDown() + SmallFlatModel.objects.all().delete() + + +class TestSmallFlatDocFilterByForeignKey(SmallFlatDocTest, TestCase): + def setUp(self): + super().setUp() + self.fks = [] + for doc in self.documents: + model = SmallFlatModelFk(**doc) + foreign_key_model = ForeignKeyModel.objects.create(name="foreign_key_name") + self.fks.append(foreign_key_model) + foreign_key_model.save() + model.field_fk = foreign_key_model + model.save() + + def do_task(self): + for fk in self.fks: + list(SmallFlatModelFk.objects.filter(field_fk__id=fk.id)) + + def tearDown(self): + super().tearDown() + SmallFlatModelFk.objects.all().delete() + ForeignKeyModel.objects.all().delete() + + +class LargeFlatDocTest(PerformanceTest): + dataset = "large_doc.json" + + def setUp(self): + super().setUp() + with open(Path(TEST_PATH) / Path("flat_models") / self.dataset) as data: # noqa: PTH123 + self.document = json.load(data) + + self.data_size = len(encode(self.document)) * NUM_DOCS + self.documents = [self.document.copy() for _ in range(NUM_DOCS)] + + +class TestLargeFlatDocCreation(LargeFlatDocTest, TestCase): + def do_task(self): + for doc in self.documents: + LargeFlatModel.objects.create(**doc) + + def after(self): + LargeFlatModel.objects.all().delete() + + +class TestLargeFlatDocUpdate(LargeFlatDocTest, TestCase): + def setUp(self): + super().setUp() + for doc in self.documents: + LargeFlatModel.objects.create(**doc) + self.models = list(LargeFlatModel.objects.all()) + self.data_size = len(encode({"field1": "updated_value0"})) * NUM_DOCS + self.iteration = 0 + + def do_task(self): + for model in self.models: + model.field1 = "updated_value" + str(self.iteration) + model.save() + self.iteration += 1 + + def tearDown(self): + LargeFlatModel.objects.all().delete() + + +class LargeNestedDocTest(PerformanceTest): + dataset = "large_doc_nested.json" + + def setUp(self): + super().setUp() + with open(Path(TEST_PATH) / Path("nested_models") / self.dataset) as data: # noqa: PTH123 + self.document = json.load(data) + + self.data_size = len(encode(self.document)) * NUM_DOCS + self.documents = [self.document.copy() for _ in range(NUM_DOCS)] + + def create_model(self): + for doc in self.documents: + model = LargeNestedModel() + for k, v in doc.items(): + if "array" in k: + array_models = [] + for item in v: + embedded_str_model = StringEmbeddedModel(**item) + embedded_str_model.unique_id = ObjectId() + array_models.append(embedded_str_model) + setattr(model, k, array_models) + elif "str" in k: + embedded_str_model = StringEmbeddedModel(**v) + embedded_str_model.unique_id = ObjectId() + setattr(model, k, embedded_str_model) + else: + embedded_int_model = IntegerEmbeddedModel(**v) + embedded_int_model.unique_id = ObjectId() + setattr(model, k, embedded_int_model) + model.save() + + +class TestLargeNestedDocCreation(LargeNestedDocTest, TestCase): + def do_task(self): + self.create_model() + + def after(self): + LargeNestedModel.objects.all().delete() + + +class TestLargeNestedDocUpdate(LargeNestedDocTest, TestCase): + def setUp(self): + super().setUp() + self.create_model() + self.models = list(LargeNestedModel.objects.all()) + self.data_size = len(encode({"field1": "updated_value0"})) * NUM_DOCS + self.iteration = 0 + + def do_task(self): + for model in self.models: + model.embedded_str_doc_1.field1 = "updated_value" + str(self.iteration) + model.save() + self.iteration += 1 + + def tearDown(self): + LargeNestedModel.objects.all().delete() + + +class TestLargeNestedDocFilterById(LargeNestedDocTest, TestCase): + def setUp(self): + super().setUp() + self.create_model() + self.ids = [ + model.embedded_str_doc_1.unique_id for model in list(LargeNestedModel.objects.all()) + ] + + def do_task(self): + for _id in self.ids: + list(LargeNestedModel.objects.filter(embedded_str_doc_1__unique_id=_id)) + + def tearDown(self): + super().tearDown() + LargeNestedModel.objects.all().delete() + + +class TestLargeNestedDocFilterArray(LargeNestedDocTest, TestCase): + def setUp(self): + super().setUp() + self.create_model() + self.ids = [ + model.embedded_str_doc_array[0].unique_id + for model in list(LargeNestedModel.objects.all()) + ] + + def do_task(self): + for _id in self.ids: + list(LargeNestedModel.objects.filter(embedded_str_doc_array__unique_id__in=[_id])) + + def tearDown(self): + super().tearDown() + LargeNestedModel.objects.all().delete()