summaryrefslogtreecommitdiff
path: root/testing/tests/perf
diff options
context:
space:
mode:
Diffstat (limited to 'testing/tests/perf')
-rw-r--r--testing/tests/perf/assets/cert_default.conf15
-rw-r--r--testing/tests/perf/conftest.py249
-rw-r--r--testing/tests/perf/pytest.ini2
-rw-r--r--testing/tests/perf/test_crypto.py81
-rw-r--r--testing/tests/perf/test_encdecpool.py78
-rw-r--r--testing/tests/perf/test_misc.py6
-rw-r--r--testing/tests/perf/test_sqlcipher.py38
-rw-r--r--testing/tests/perf/test_sync.py68
8 files changed, 537 insertions, 0 deletions
diff --git a/testing/tests/perf/assets/cert_default.conf b/testing/tests/perf/assets/cert_default.conf
new file mode 100644
index 00000000..8043cea3
--- /dev/null
+++ b/testing/tests/perf/assets/cert_default.conf
@@ -0,0 +1,15 @@
+[ req ]
+default_bits = 1024
+default_keyfile = keyfile.pem
+distinguished_name = req_distinguished_name
+prompt = no
+output_password = mypass
+
+[ req_distinguished_name ]
+C = GB
+ST = Test State or Province
+L = Test Locality
+O = Organization Name
+OU = Organizational Unit Name
+CN = localhost
+emailAddress = test@email.address
diff --git a/testing/tests/perf/conftest.py b/testing/tests/perf/conftest.py
new file mode 100644
index 00000000..5ac1f3c0
--- /dev/null
+++ b/testing/tests/perf/conftest.py
@@ -0,0 +1,249 @@
+import json
+import os
+import pytest
+import requests
+import random
+import base64
+import signal
+import time
+
+from hashlib import sha512
+from uuid import uuid4
+from subprocess import call
+from urlparse import urljoin
+from twisted.internet import threads, reactor
+
+from leap.soledad.client import Soledad
+from leap.soledad.common.couch import CouchDatabase
+
+
+# we have to manually setup the events server in order to be able to signal
+# events. This is usually done by the enclosing application using soledad
+# client (i.e. bitmask client).
+from leap.common.events import server
+server.ensure_server()
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--couch-url", type="string", default="http://127.0.0.1:5984",
+ help="the url for the couch server to be used during tests")
+ parser.addoption(
+ "--num-docs", type="int", default=100,
+ help="the number of documents to use in performance tests")
+
+
+#
+# default options for all tests
+#
+
+DEFAULT_PASSPHRASE = '123'
+
+DEFAULT_URL = 'http://127.0.0.1:2424'
+DEFAULT_PRIVKEY = 'soledad_privkey.pem'
+DEFAULT_CERTKEY = 'soledad_certkey.pem'
+DEFAULT_TOKEN = 'an-auth-token'
+
+
+@pytest.fixture()
+def payload():
+ def generate(size):
+ random.seed(1337) # same seed to avoid different bench results
+ payload_bytes = bytearray(random.getrandbits(8) for _ in xrange(size))
+ # encode as base64 to avoid ascii encode/decode errors
+ return base64.b64encode(payload_bytes)[:size] # remove b64 overhead
+ return generate
+
+
+#
+# soledad_dbs fixture: provides all databases needed by soledad server in a per
+# module scope (same databases for all tests in this module).
+#
+
+def _token_dbname():
+ dbname = 'tokens_' + \
+ str(int(time.time() / (30 * 24 * 3600)))
+ return dbname
+
+
+class SoledadDatabases(object):
+
+ def __init__(self, url):
+ self._token_db_url = urljoin(url, _token_dbname())
+ self._shared_db_url = urljoin(url, 'shared')
+
+ def setup(self, uuid):
+ self._create_dbs()
+ self._add_token(uuid)
+
+ def _create_dbs(self):
+ requests.put(self._token_db_url)
+ requests.put(self._shared_db_url)
+
+ def _add_token(self, uuid):
+ token = sha512(DEFAULT_TOKEN).hexdigest()
+ content = {'type': 'Token', 'user_id': uuid}
+ requests.put(
+ self._token_db_url + '/' + token, data=json.dumps(content))
+
+ def teardown(self):
+ requests.delete(self._token_db_url)
+ requests.delete(self._shared_db_url)
+
+
+@pytest.fixture()
+def soledad_dbs(request):
+ couch_url = request.config.option.couch_url
+
+ def create(uuid):
+ db = SoledadDatabases(couch_url)
+ request.addfinalizer(db.teardown)
+ return db.setup(uuid)
+ return create
+
+
+#
+# remote_db fixture: provides an empty database for a given user in a per
+# function scope.
+#
+
+class UserDatabase(object):
+
+ def __init__(self, url, uuid):
+ self._remote_db_url = urljoin(url, 'user-%s' % uuid)
+
+ def setup(self):
+ return CouchDatabase.open_database(
+ url=self._remote_db_url, create=True, replica_uid=None)
+
+ def teardown(self):
+ requests.delete(self._remote_db_url)
+
+
+@pytest.fixture()
+def remote_db(request):
+ couch_url = request.config.option.couch_url
+
+ def create(uuid):
+ db = UserDatabase(couch_url, uuid)
+ request.addfinalizer(db.teardown)
+ return db.setup()
+ return create
+
+
+def get_pid(pidfile):
+ if not os.path.isfile(pidfile):
+ return 0
+ try:
+ with open(pidfile) as f:
+ return int(f.read())
+ except IOError:
+ return 0
+
+
+#
+# soledad_server fixture: provides a running soledad server in a per module
+# context (same soledad server for all tests in this module).
+#
+
+class SoledadServer(object):
+
+ def __init__(self, tmpdir_factory, couch_url):
+ tmpdir = tmpdir_factory.mktemp('soledad-server')
+ self._pidfile = os.path.join(tmpdir.strpath, 'soledad-server.pid')
+ self._logfile = os.path.join(tmpdir.strpath, 'soledad-server.log')
+ self._couch_url = couch_url
+
+ def start(self):
+ self._create_conf_file()
+ # start the server
+ call([
+ 'twistd',
+ '--logfile=%s' % self._logfile,
+ '--pidfile=%s' % self._pidfile,
+ 'web',
+ '--wsgi=leap.soledad.server.application',
+ '--port=2424'
+ ])
+
+ def _create_conf_file(self):
+ if not os.access('/etc', os.W_OK):
+ return
+ if not os.path.isdir('/etc/soledad'):
+ os.mkdir('/etc/soledad')
+ with open('/etc/soledad/soledad-server.conf', 'w') as f:
+ content = '[soledad-server]\ncouch_url = %s' % self._couch_url
+ f.write(content)
+
+ def stop(self):
+ pid = get_pid(self._pidfile)
+ os.kill(pid, signal.SIGKILL)
+
+
+@pytest.fixture(scope='module')
+def soledad_server(tmpdir_factory, request):
+ couch_url = request.config.option.couch_url
+ server = SoledadServer(tmpdir_factory, couch_url)
+ server.start()
+ request.addfinalizer(server.stop)
+ return server
+
+
+@pytest.fixture()
+def txbenchmark(benchmark):
+ def blockOnThread(*args, **kwargs):
+ return threads.deferToThread(
+ benchmark, threads.blockingCallFromThread,
+ reactor, *args, **kwargs)
+ return blockOnThread
+
+
+@pytest.fixture()
+def txbenchmark_with_setup(benchmark):
+ def blockOnThreadWithSetup(setup, f):
+ def blocking_runner(*args, **kwargs):
+ return threads.blockingCallFromThread(reactor, f, *args, **kwargs)
+
+ def blocking_setup():
+ args = threads.blockingCallFromThread(reactor, setup)
+ try:
+ return tuple(arg for arg in args), {}
+ except TypeError:
+ return ((args,), {}) if args else None
+
+ def bench():
+ return benchmark.pedantic(blocking_runner, setup=blocking_setup,
+ rounds=4, warmup_rounds=1)
+ return threads.deferToThread(bench)
+ return blockOnThreadWithSetup
+
+
+#
+# soledad_client fixture: provides a clean soledad client for a test function.
+#
+
+@pytest.fixture()
+def soledad_client(tmpdir, soledad_server, remote_db, soledad_dbs, request):
+ passphrase = DEFAULT_PASSPHRASE
+ server_url = DEFAULT_URL
+ token = DEFAULT_TOKEN
+ default_uuid = uuid4().hex
+ remote_db(default_uuid)
+ soledad_dbs(default_uuid)
+
+ # get a soledad instance
+ def create():
+ secrets_path = os.path.join(tmpdir.strpath, '%s.secret' % uuid4().hex)
+ local_db_path = os.path.join(tmpdir.strpath, '%s.db' % uuid4().hex)
+ soledad_client = Soledad(
+ default_uuid,
+ unicode(passphrase),
+ secrets_path=secrets_path,
+ local_db_path=local_db_path,
+ server_url=server_url,
+ cert_file=None,
+ auth_token=token,
+ defer_encryption=True)
+ request.addfinalizer(soledad_client.close)
+ return soledad_client
+ return create
diff --git a/testing/tests/perf/pytest.ini b/testing/tests/perf/pytest.ini
new file mode 100644
index 00000000..7a0508ce
--- /dev/null
+++ b/testing/tests/perf/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+twisted = yes
diff --git a/testing/tests/perf/test_crypto.py b/testing/tests/perf/test_crypto.py
new file mode 100644
index 00000000..be00560b
--- /dev/null
+++ b/testing/tests/perf/test_crypto.py
@@ -0,0 +1,81 @@
+import pytest
+import json
+from uuid import uuid4
+from leap.soledad.common.document import SoledadDocument
+from leap.soledad.client.crypto import encrypt_sym
+from leap.soledad.client.crypto import decrypt_sym
+
+
+def create_doc_encryption(size):
+ @pytest.mark.benchmark(group="test_crypto_encrypt_doc")
+ def test_doc_encryption(soledad_client, benchmark, payload):
+ crypto = soledad_client()._crypto
+
+ DOC_CONTENT = {'payload': payload(size)}
+ doc = SoledadDocument(
+ doc_id=uuid4().hex, rev='rev',
+ json=json.dumps(DOC_CONTENT))
+
+ benchmark(crypto.encrypt_doc, doc)
+ return test_doc_encryption
+
+
+def create_doc_decryption(size):
+ @pytest.mark.benchmark(group="test_crypto_decrypt_doc")
+ def test_doc_decryption(soledad_client, benchmark, payload):
+ crypto = soledad_client()._crypto
+
+ DOC_CONTENT = {'payload': payload(size)}
+ doc = SoledadDocument(
+ doc_id=uuid4().hex, rev='rev',
+ json=json.dumps(DOC_CONTENT))
+ encrypted_doc = crypto.encrypt_doc(doc)
+ doc.set_json(encrypted_doc)
+
+ benchmark(crypto.decrypt_doc, doc)
+ return test_doc_decryption
+
+
+test_encrypt_doc_10k = create_doc_encryption(10*1000)
+test_encrypt_doc_100k = create_doc_encryption(100*1000)
+test_encrypt_doc_500k = create_doc_encryption(500*1000)
+test_encrypt_doc_1M = create_doc_encryption(1000*1000)
+test_encrypt_doc_10M = create_doc_encryption(10*1000*1000)
+test_encrypt_doc_50M = create_doc_encryption(50*1000*1000)
+test_decrypt_doc_10k = create_doc_decryption(10*1000)
+test_decrypt_doc_100k = create_doc_decryption(100*1000)
+test_decrypt_doc_500k = create_doc_decryption(500*1000)
+test_decrypt_doc_1M = create_doc_decryption(1000*1000)
+test_decrypt_doc_10M = create_doc_decryption(10*1000*1000)
+test_decrypt_doc_50M = create_doc_decryption(50*1000*1000)
+
+
+def create_raw_encryption(size):
+ @pytest.mark.benchmark(group="test_crypto_raw_encrypt")
+ def test_raw_encrypt(benchmark, payload):
+ key = payload(32)
+ benchmark(encrypt_sym, payload(size), key)
+ return test_raw_encrypt
+
+
+def create_raw_decryption(size):
+ @pytest.mark.benchmark(group="test_crypto_raw_decrypt")
+ def test_raw_decrypt(benchmark, payload):
+ key = payload(32)
+ iv, ciphertext = encrypt_sym(payload(size), key)
+ benchmark(decrypt_sym, ciphertext, key, iv)
+ return test_raw_decrypt
+
+
+test_encrypt_raw_10k = create_raw_encryption(10*1000)
+test_encrypt_raw_100k = create_raw_encryption(100*1000)
+test_encrypt_raw_500k = create_raw_encryption(500*1000)
+test_encrypt_raw_1M = create_raw_encryption(1000*1000)
+test_encrypt_raw_10M = create_raw_encryption(10*1000*1000)
+test_encrypt_raw_50M = create_raw_encryption(50*1000*1000)
+test_decrypt_raw_10k = create_raw_decryption(10*1000)
+test_decrypt_raw_100k = create_raw_decryption(100*1000)
+test_decrypt_raw_500k = create_raw_decryption(500*1000)
+test_decrypt_raw_1M = create_raw_decryption(1000*1000)
+test_decrypt_raw_10M = create_raw_decryption(10*1000*1000)
+test_decrypt_raw_50M = create_raw_decryption(50*1000*1000)
diff --git a/testing/tests/perf/test_encdecpool.py b/testing/tests/perf/test_encdecpool.py
new file mode 100644
index 00000000..77091a41
--- /dev/null
+++ b/testing/tests/perf/test_encdecpool.py
@@ -0,0 +1,78 @@
+import pytest
+import json
+from uuid import uuid4
+from twisted.internet.defer import gatherResults
+from leap.soledad.client.encdecpool import SyncEncrypterPool
+from leap.soledad.client.encdecpool import SyncDecrypterPool
+from leap.soledad.common.document import SoledadDocument
+# FIXME: test load is low due issue #7370, higher values will get out of memory
+
+
+def create_encrypt(amount, size):
+ @pytest.mark.benchmark(group="test_pool_encrypt")
+ @pytest.inlineCallbacks
+ def test(soledad_client, txbenchmark_with_setup, request, payload):
+ DOC_CONTENT = {'payload': payload(size)}
+
+ def setup():
+ client = soledad_client()
+ pool = SyncEncrypterPool(client._crypto, client._sync_db)
+ pool.start()
+ request.addfinalizer(pool.stop)
+ docs = [
+ SoledadDocument(doc_id=uuid4().hex, rev='rev',
+ json=json.dumps(DOC_CONTENT))
+ for _ in xrange(amount)
+ ]
+ return pool, docs
+
+ @pytest.inlineCallbacks
+ def put_and_wait(pool, docs):
+ yield gatherResults([pool.encrypt_doc(doc) for doc in docs])
+
+ yield txbenchmark_with_setup(setup, put_and_wait)
+ return test
+
+test_encdecpool_encrypt_100_10k = create_encrypt(100, 10*1000)
+test_encdecpool_encrypt_100_100k = create_encrypt(100, 100*1000)
+test_encdecpool_encrypt_100_500k = create_encrypt(100, 500*1000)
+
+
+def create_decrypt(amount, size):
+ @pytest.mark.benchmark(group="test_pool_decrypt")
+ @pytest.inlineCallbacks
+ def test(soledad_client, txbenchmark_with_setup, request, payload):
+ DOC_CONTENT = {'payload': payload(size)}
+ client = soledad_client()
+
+ def setup():
+ pool = SyncDecrypterPool(
+ client._crypto,
+ client._sync_db,
+ source_replica_uid=client._dbpool.replica_uid,
+ insert_doc_cb=lambda x, y, z: False) # ignored
+ pool.start(amount)
+ request.addfinalizer(pool.stop)
+ crypto = client._crypto
+ docs = []
+ for _ in xrange(amount):
+ doc = SoledadDocument(
+ doc_id=uuid4().hex, rev='rev',
+ json=json.dumps(DOC_CONTENT))
+ encrypted_content = json.loads(crypto.encrypt_doc(doc))
+ docs.append((doc.doc_id, encrypted_content))
+ return pool, docs
+
+ def put_and_wait(pool, docs):
+ deferreds = [] # fires on completion
+ for idx, (doc_id, content) in enumerate(docs, 1):
+ deferreds.append(pool.insert_encrypted_received_doc(
+ doc_id, 'rev', content, idx, "trans_id", idx))
+ return gatherResults(deferreds)
+
+ yield txbenchmark_with_setup(setup, put_and_wait)
+ return test
+
+test_encdecpool_decrypt_100_10k = create_decrypt(100, 10*1000)
+test_encdecpool_decrypt_100_100k = create_decrypt(100, 100*1000)
+test_encdecpool_decrypt_100_500k = create_decrypt(100, 500*1000)
diff --git a/testing/tests/perf/test_misc.py b/testing/tests/perf/test_misc.py
new file mode 100644
index 00000000..ead48adf
--- /dev/null
+++ b/testing/tests/perf/test_misc.py
@@ -0,0 +1,6 @@
+import pytest
+
+
+@pytest.mark.benchmark(group="test_instance")
+def test_initialization(soledad_client, benchmark):
+ benchmark(soledad_client)
diff --git a/testing/tests/perf/test_sqlcipher.py b/testing/tests/perf/test_sqlcipher.py
new file mode 100644
index 00000000..e7a54228
--- /dev/null
+++ b/testing/tests/perf/test_sqlcipher.py
@@ -0,0 +1,38 @@
+'''
+Tests SoledadClient/SQLCipher interaction
+'''
+import pytest
+
+from twisted.internet.defer import gatherResults
+
+
+def load_up(client, amount, payload, defer=True):
+ results = [client.create_doc({'content': payload}) for _ in xrange(amount)]
+ if defer:
+ return gatherResults(results)
+
+
+def build_test_sqlcipher_async_create(amount, size):
+ @pytest.inlineCallbacks
+ @pytest.mark.benchmark(group="test_sqlcipher_async_create")
+ def test(soledad_client, txbenchmark, payload):
+ client = soledad_client()
+ yield txbenchmark(load_up, client, amount, payload(size))
+ return test
+
+
+def build_test_sqlcipher_create(amount, size):
+ @pytest.mark.benchmark(group="test_sqlcipher_create")
+ def test(soledad_client, benchmark, payload):
+ client = soledad_client()._dbsyncer
+ benchmark(load_up, client, amount, payload(size), defer=False)
+ return test
+
+
+test_async_create_20_500k = build_test_sqlcipher_async_create(20, 500*1000)
+test_async_create_100_100k = build_test_sqlcipher_async_create(100, 100*1000)
+test_async_create_1000_10k = build_test_sqlcipher_async_create(1000, 10*1000)
+# synchronous
+test_create_20_500k = build_test_sqlcipher_create(20, 500*1000)
+test_create_100_100k = build_test_sqlcipher_create(100, 100*1000)
+test_create_1000_10k = build_test_sqlcipher_create(1000, 10*1000)
diff --git a/testing/tests/perf/test_sync.py b/testing/tests/perf/test_sync.py
new file mode 100644
index 00000000..0b48a0b9
--- /dev/null
+++ b/testing/tests/perf/test_sync.py
@@ -0,0 +1,68 @@
+import pytest
+
+from twisted.internet.defer import gatherResults
+
+
+def load_up(client, amount, payload):
+ deferreds = []
+ # create a bunch of local documents
+ for i in xrange(amount):
+ d = client.create_doc({'content': payload})
+ deferreds.append(d)
+ d = gatherResults(deferreds)
+ d.addCallback(lambda _: None)
+ return d
+
+
+def create_upload(uploads, size):
+ @pytest.inlineCallbacks
+ @pytest.mark.benchmark(group="test_upload")
+ def test(soledad_client, txbenchmark_with_setup, payload):
+ client = soledad_client()
+
+ def setup():
+ return load_up(client, uploads, payload(size))
+
+ yield txbenchmark_with_setup(setup, client.sync)
+ return test
+
+
+test_upload_20_500k = create_upload(20, 500*1000)
+test_upload_100_100k = create_upload(100, 100*1000)
+test_upload_1000_10k = create_upload(1000, 10*1000)
+
+
+def create_download(downloads, size):
+ @pytest.inlineCallbacks
+ @pytest.mark.benchmark(group="test_download")
+ def test(soledad_client, txbenchmark_with_setup, payload):
+ client = soledad_client()
+
+ yield load_up(client, downloads, payload(size))
+ yield client.sync()
+ # We could create them directly on couch, but sending them
+ # ensures we are dealing with properly encrypted docs
+
+ def setup():
+ return soledad_client()
+
+ def sync(clean_client):
+ return clean_client.sync()
+ yield txbenchmark_with_setup(setup, sync)
+ return test
+
+
+test_download_20_500k = create_download(20, 500*1000)
+test_download_100_100k = create_download(100, 100*1000)
+test_download_1000_10k = create_download(1000, 10*1000)
+
+
+@pytest.inlineCallbacks
+@pytest.mark.benchmark(group="test_nothing_to_sync")
+def test_nothing_to_sync(soledad_client, txbenchmark_with_setup):
+ def setup():
+ return soledad_client()
+
+ def sync(clean_client):
+ return clean_client.sync()
+ yield txbenchmark_with_setup(setup, sync)