2019-01-22 23:45:13 +01:00
|
|
|
import os
|
2019-07-17 04:32:05 +02:00
|
|
|
import sys
|
2019-01-22 23:45:13 +01:00
|
|
|
import json
|
2018-12-12 07:43:17 +01:00
|
|
|
import argparse
|
2018-12-19 20:46:47 +01:00
|
|
|
import asyncio
|
2018-12-11 19:04:32 +01:00
|
|
|
import time
|
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
import aiohttp
|
2018-12-19 20:46:47 +01:00
|
|
|
from aiohttp import ClientConnectorError
|
2019-06-21 03:02:58 +02:00
|
|
|
from lbry import __version__
|
|
|
|
from lbry.blob.blob_file import MAX_BLOB_SIZE
|
|
|
|
from lbry.conf import Config
|
|
|
|
from lbry.extras.daemon.client import daemon_rpc
|
|
|
|
from lbry.extras import system_info
|
2018-12-11 19:04:32 +01:00
|
|
|
|
|
|
|
|
2019-01-22 23:45:13 +01:00
|
|
|
async def report_to_slack(output, webhook):
|
|
|
|
payload = {
|
|
|
|
"text": f"lbrynet {__version__} ({system_info.get_platform()['platform']}) time to first byte:\n{output}"
|
|
|
|
}
|
|
|
|
async with aiohttp.request('post', webhook, data=json.dumps(payload)):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-02-20 21:10:36 +01:00
|
|
|
def confidence(times, z, plus_err=True):
|
2019-01-22 23:45:13 +01:00
|
|
|
mean = sum(times) / len(times)
|
2019-10-02 18:38:56 +02:00
|
|
|
standard_dev = (sum((t - sum(times) / len(times)) ** 2.0 for t in times) / len(times)) ** 0.5
|
2019-01-22 23:45:13 +01:00
|
|
|
err = (z * standard_dev) / (len(times) ** 0.5)
|
2019-02-20 21:10:36 +01:00
|
|
|
return f"{round((mean + err) if plus_err else (mean - err), 3)}"
|
2019-01-22 23:45:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
def variance(times):
|
|
|
|
mean = sum(times) / len(times)
|
2019-10-02 18:38:56 +02:00
|
|
|
return round(sum((i - mean) ** 2.0 for i in times) / (len(times) - 1), 3)
|
2019-01-22 23:45:13 +01:00
|
|
|
|
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
async def wait_for_done(conf, claim_name, timeout):
|
2019-07-29 03:18:29 +02:00
|
|
|
blobs_completed, last_completed = 0, time.perf_counter()
|
2019-01-22 23:45:13 +01:00
|
|
|
while True:
|
2019-07-17 04:32:05 +02:00
|
|
|
file = (await daemon_rpc(conf, "file_list", claim_name=claim_name))[0]
|
2019-01-22 23:45:13 +01:00
|
|
|
if file['status'] in ['finished', 'stopped']:
|
2019-02-09 02:14:10 +01:00
|
|
|
return True, file['blobs_completed'], file['blobs_in_stream']
|
2019-07-17 04:32:05 +02:00
|
|
|
elif blobs_completed < int(file['blobs_completed']):
|
2019-07-29 03:18:29 +02:00
|
|
|
blobs_completed, last_completed = int(file['blobs_completed']), time.perf_counter()
|
|
|
|
elif (time.perf_counter() - last_completed) > timeout:
|
2019-02-09 02:14:10 +01:00
|
|
|
return False, file['blobs_completed'], file['blobs_in_stream']
|
2019-07-17 04:32:05 +02:00
|
|
|
await asyncio.sleep(1.0)
|
2019-01-22 23:45:13 +01:00
|
|
|
|
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
async def main(cmd_args=None):
|
|
|
|
print('Time to first byte started using parameters:')
|
|
|
|
for key, value in vars(cmd_args).items():
|
|
|
|
print(f"{key}: {value}")
|
2019-01-23 22:41:14 +01:00
|
|
|
conf = Config()
|
2019-07-17 04:32:05 +02:00
|
|
|
url_to_claim = {}
|
2018-12-19 20:46:47 +01:00
|
|
|
try:
|
2019-07-17 04:32:05 +02:00
|
|
|
for page in range(1, cmd_args.download_pages + 1):
|
2019-07-29 03:18:13 +02:00
|
|
|
start = time.perf_counter()
|
|
|
|
kwargs = {
|
|
|
|
'page': page,
|
2019-10-14 18:59:59 +02:00
|
|
|
'claim_type': 'stream',
|
2019-10-18 15:35:10 +02:00
|
|
|
'any_tags': [
|
|
|
|
'art',
|
|
|
|
'automotive',
|
|
|
|
'blockchain',
|
|
|
|
'comedy',
|
|
|
|
'economics',
|
|
|
|
'education',
|
|
|
|
'gaming',
|
|
|
|
'music',
|
|
|
|
'news',
|
|
|
|
'science',
|
|
|
|
'sports',
|
|
|
|
'technology',
|
|
|
|
],
|
2019-07-29 03:42:04 +02:00
|
|
|
'order_by': ['trending_global', 'trending_mixed'],
|
2019-07-29 03:18:13 +02:00
|
|
|
'no_totals': True
|
|
|
|
}
|
|
|
|
|
2019-10-18 15:35:10 +02:00
|
|
|
if not cmd_args.allow_fees:
|
|
|
|
kwargs['fee_amount'] = 0
|
2019-07-29 03:18:13 +02:00
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
response = await daemon_rpc(
|
2019-07-29 03:18:13 +02:00
|
|
|
conf, 'claim_search', **kwargs
|
2019-07-17 04:32:05 +02:00
|
|
|
)
|
|
|
|
if 'error' in response or not response.get('items'):
|
|
|
|
print(f'Error getting claim list page {page}:')
|
|
|
|
print(response)
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
url_to_claim.update({
|
2019-08-12 06:49:33 +02:00
|
|
|
claim['permanent_url']: claim for claim in response['items'] if claim['value_type'] == 'stream'
|
2019-07-17 04:32:05 +02:00
|
|
|
})
|
2019-07-29 03:18:29 +02:00
|
|
|
print(f'Claim search page {page} took: {time.perf_counter() - start}')
|
2018-12-19 20:46:47 +01:00
|
|
|
except (ClientConnectorError, ConnectionError):
|
2019-01-22 23:45:13 +01:00
|
|
|
print("Could not connect to daemon")
|
|
|
|
return 1
|
|
|
|
print("**********************************************")
|
2019-07-17 04:32:05 +02:00
|
|
|
print(f"Attempting to download {len(url_to_claim)} claim_search streams")
|
2018-12-11 19:04:32 +01:00
|
|
|
|
2019-01-11 21:01:56 +01:00
|
|
|
first_byte_times = []
|
2019-02-20 20:16:51 +01:00
|
|
|
download_speeds = []
|
|
|
|
download_successes = []
|
2019-07-17 04:32:05 +02:00
|
|
|
failed_to = {}
|
2018-12-11 19:04:32 +01:00
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
await asyncio.gather(*(
|
|
|
|
daemon_rpc(conf, 'file_delete', delete_from_download_dir=True, claim_name=claim['name'])
|
|
|
|
for claim in url_to_claim.values() if not cmd_args.keep_files
|
|
|
|
))
|
2019-01-11 21:01:56 +01:00
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
for i, (url, claim) in enumerate(url_to_claim.items()):
|
2019-07-29 03:18:29 +02:00
|
|
|
start = time.perf_counter()
|
2019-07-17 04:32:05 +02:00
|
|
|
response = await daemon_rpc(conf, 'get', uri=url, save_file=not cmd_args.head_blob_only)
|
|
|
|
if 'error' in response:
|
|
|
|
print(f"{i + 1}/{len(url_to_claim)} - failed to start {url}: {response['error']}")
|
|
|
|
failed_to[url] = 'start'
|
2019-05-01 18:17:08 +02:00
|
|
|
if cmd_args.exit_on_error:
|
|
|
|
return
|
2019-07-17 04:32:05 +02:00
|
|
|
continue
|
2019-07-29 03:18:29 +02:00
|
|
|
first_byte = time.perf_counter()
|
2019-07-17 04:32:05 +02:00
|
|
|
first_byte_times.append(first_byte - start)
|
|
|
|
print(f"{i + 1}/{len(url_to_claim)} - {first_byte - start} {url}")
|
|
|
|
if not cmd_args.head_blob_only:
|
|
|
|
downloaded, amount_downloaded, blobs_in_stream = await wait_for_done(
|
|
|
|
conf, claim['name'], cmd_args.stall_download_timeout
|
|
|
|
)
|
|
|
|
if downloaded:
|
|
|
|
download_successes.append(url)
|
|
|
|
else:
|
|
|
|
failed_to[url] = 'finish'
|
2019-07-29 03:18:29 +02:00
|
|
|
mbs = round((blobs_in_stream * (MAX_BLOB_SIZE - 1)) / (time.perf_counter() - start) / 1000000, 2)
|
2019-07-17 04:32:05 +02:00
|
|
|
download_speeds.append(mbs)
|
|
|
|
print(f"downloaded {amount_downloaded}/{blobs_in_stream} blobs for {url} at "
|
|
|
|
f"{mbs}mb/s")
|
|
|
|
if not cmd_args.keep_files:
|
|
|
|
await daemon_rpc(conf, 'file_delete', delete_from_download_dir=True, claim_name=claim['name'])
|
2019-01-22 23:45:13 +01:00
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
|
|
|
print("**********************************************")
|
2019-07-17 04:32:05 +02:00
|
|
|
result = f"Started {len(first_byte_times)} of {len(url_to_claim)} attempted front page streams\n"
|
|
|
|
if first_byte_times:
|
|
|
|
result += f"Worst first byte time: {round(max(first_byte_times), 2)}\n" \
|
|
|
|
f"Best first byte time: {round(min(first_byte_times), 2)}\n" \
|
2019-08-12 06:49:33 +02:00
|
|
|
f"*95% confidence time-to-first-byte: {confidence(first_byte_times, 1.984)}s*\n" \
|
2019-07-17 04:32:05 +02:00
|
|
|
f"99% confidence time-to-first-byte: {confidence(first_byte_times, 2.626)}s\n" \
|
|
|
|
f"Variance: {variance(first_byte_times)}\n"
|
|
|
|
if download_successes:
|
|
|
|
result += f"Downloaded {len(download_successes)}/{len(url_to_claim)}\n" \
|
2019-05-01 19:31:11 +02:00
|
|
|
f"Best stream download speed: {round(max(download_speeds), 2)}mb/s\n" \
|
|
|
|
f"Worst stream download speed: {round(min(download_speeds), 2)}mb/s\n" \
|
|
|
|
f"95% confidence download speed: {confidence(download_speeds, 1.984, False)}mb/s\n" \
|
|
|
|
f"99% confidence download speed: {confidence(download_speeds, 2.626, False)}mb/s\n"
|
2019-02-20 20:16:51 +01:00
|
|
|
|
2019-07-17 04:32:05 +02:00
|
|
|
for reason in ('start', 'finish'):
|
|
|
|
failures = [url for url, why in failed_to.items() if reason == why]
|
|
|
|
if failures:
|
2019-08-12 06:49:33 +02:00
|
|
|
result += f"\nFailed to {reason}:\n" + "\n•".join(failures)
|
2019-01-22 23:45:13 +01:00
|
|
|
print(result)
|
2019-01-31 18:34:25 +01:00
|
|
|
|
2019-02-20 20:16:51 +01:00
|
|
|
webhook = os.environ.get('TTFB_SLACK_TOKEN', None)
|
|
|
|
if webhook:
|
|
|
|
await report_to_slack(result, webhook)
|
2019-07-17 04:32:05 +02:00
|
|
|
return 0
|
2018-12-11 19:04:32 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2018-12-12 07:43:17 +01:00
|
|
|
parser = argparse.ArgumentParser()
|
2019-02-20 20:16:51 +01:00
|
|
|
parser.add_argument("--allow_fees", action='store_true')
|
2019-05-01 18:17:08 +02:00
|
|
|
parser.add_argument("--exit_on_error", action='store_true')
|
2019-10-14 18:59:59 +02:00
|
|
|
parser.add_argument("--stall_download_timeout", default=5, type=int)
|
2019-07-17 04:32:05 +02:00
|
|
|
parser.add_argument("--keep_files", action='store_true')
|
2019-05-01 18:59:45 +02:00
|
|
|
parser.add_argument("--head_blob_only", action='store_true')
|
2019-07-17 04:32:05 +02:00
|
|
|
parser.add_argument("--download_pages", type=int, default=10)
|
|
|
|
sys.exit(asyncio.run(main(cmd_args=parser.parse_args())) or 0)
|