"...PrivateStorageio.git" did not exist on "652b01a58d72020ce649600ed5fee1271c0aea3e"
Newer
Older
#! /usr/bin/env nix-shell
#! nix-shell -i python -p "python.withPackages (ps: [ ps.treq ])"
# Apply a very minimal load to a PaymentServer via the redemption endpoint.
#
# This requires a PaymentServer run something like:
#
# $ ./result/bin/PaymentServer-exe --issuer Trivial --http-port 8080 --stripe-key-path ../stripe.secret --database SQLite3 --database-path ./vouchers.db
#
# It also requires some vouchers to be marked as paid somehow. The vouchers
# are aaa0 through aaaN where N is the PARALLELISM you select. You can
# accomplish this by:
#
# $ for n in $(seq 0 30); do sqlite3 vouchers.db "insert into vouchers (name) values ('aaa$n')"; done
#
# Then the test can be run as many times as necessary.
#
# The `redeemed` table must be cleared before each test run. Random tokens
# are generated for each test run and the server will reject tokens from a new
# run if tokens from an old run are still present.
#
# $ sqlite3 vouchers.db "delete from redeemed"
#
# Originally written for https://github.com/PrivateStorageio/PaymentServer/issues/60
from __future__ import division
from os import (
urandom,
)
from base64 import (
b64encode,
)
from time import (
time,
)
from json import (
dumps,
)
from treq.client import (
HTTPClient,
)
from twisted.web.client import (
Agent,
readBody,
)
from twisted.internet.task import (
react,
)
from twisted.internet.defer import (
inlineCallbacks,
gatherResults,
returnValue,
)
ITERATIONS = 16
NUM_TOKENS = 5000
def a_random_token():
return b64encode(urandom(32))
def tokens_for_voucher(key, cache={}):
if key not in cache:
print("Generating tokens for {}".format(key))
cache[key] = list(
a_random_token()
for _
in range(NUM_TOKENS)
)
else:
print("Using cached tokens for {}".format(key))
return cache[key]
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@inlineCallbacks
def redeem_with_retry(client, data, headers):
"""
Attempt a redemption. Retry if it fails.
:return: A ``Deferred`` that fires with (duration of successful request,
number of failed requests).
"""
errors = 0
while True:
before = time()
response = yield client.post(
url="http://127.0.0.1:8080/v1/redeem",
data=data,
headers=headers,
)
after = time()
duration = int((after - before) * 1000)
body = yield readBody(response)
if response.code == 200:
print("Request complete in {}ms".format(duration))
returnValue((duration, errors))
errors += 1
try:
reason = loads(body)["reason"]
except ValueError:
reason = body
print("Request failed: {} {}".format(response.code, reason))
@inlineCallbacks
def redeem(client, index):
times = []
total_errors = 0
for i in range(ITERATIONS):
tokens = tokens_for_voucher((voucher, i))
duration, errors = yield redeem_with_retry(
client,
"redeemVoucher": voucher,
"redeemTokens": tokens,
"redeemCounter": i,
}),
headers={"content-type": "application/json"},
)
times.append(duration)
total_errors += errors
returnValue((times, total_errors))
def mean(xs):
return sum(xs) / len(xs)
def percentile(n, xs):
return sorted(xs)[int(len(xs) / 100 * n)]
def median(xs):
return percentile(50, xs)
@react
@inlineCallbacks
def main(reactor):
client = HTTPClient(Agent(reactor))
ds = list(
redeem(client, i)
for i
in range(PARALLELISM)
)
times = []
total_errors = 0
for (result, errors) in (yield gatherResults(ds)):
times.extend(result)
total_errors += errors
print("min: {}".format(min(times)))
print("max: {}".format(max(times)))
print("mean: {}".format(mean(times)))
print("median: {}".format(median(times)))
print("95th: {}".format(percentile(95, times)))
print("errors: {}".format(total_errors))
print("error rate: {}".format(
total_errors / (total_errors + PARALLELISM * ITERATIONS),
))