BenchmarkPlugin.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. import os
  2. import time
  3. import io
  4. import math
  5. import hashlib
  6. import re
  7. import sys
  8. from Config import config
  9. from Crypt import CryptHash
  10. from Plugin import PluginManager
  11. from Debug import Debug
  12. from util import helper
  13. plugin_dir = os.path.dirname(__file__)
  14. benchmark_key = None
  15. @PluginManager.registerTo("UiRequest")
  16. class UiRequestPlugin(object):
  17. @helper.encodeResponse
  18. def actionBenchmark(self):
  19. global benchmark_key
  20. script_nonce = self.getScriptNonce()
  21. if not benchmark_key:
  22. benchmark_key = CryptHash.random(encoding="base64")
  23. self.sendHeader(script_nonce=script_nonce)
  24. if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
  25. yield "This function is disabled on this proxy"
  26. return
  27. data = self.render(
  28. plugin_dir + "/media/benchmark.html",
  29. script_nonce=script_nonce,
  30. benchmark_key=benchmark_key,
  31. filter=re.sub("[^A-Za-z0-9]", "", self.get.get("filter", ""))
  32. )
  33. yield data
  34. @helper.encodeResponse
  35. def actionBenchmarkResult(self):
  36. global benchmark_key
  37. if self.get.get("benchmark_key", "") != benchmark_key:
  38. return self.error403("Invalid benchmark key")
  39. self.sendHeader(content_type="text/plain", noscript=True)
  40. if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
  41. yield "This function is disabled on this proxy"
  42. return
  43. yield " " * 1024 # Head (required for streaming)
  44. import main
  45. s = time.time()
  46. for part in main.actions.testBenchmark(filter=self.get.get("filter", "")):
  47. yield part
  48. yield "\n - Total time: %.3fs" % (time.time() - s)
  49. @PluginManager.registerTo("Actions")
  50. class ActionsPlugin:
  51. def getMultiplerTitle(self, multipler):
  52. if multipler < 0.3:
  53. multipler_title = "Sloooow"
  54. elif multipler < 0.6:
  55. multipler_title = "Ehh"
  56. elif multipler < 0.8:
  57. multipler_title = "Goodish"
  58. elif multipler < 1.2:
  59. multipler_title = "OK"
  60. elif multipler < 1.7:
  61. multipler_title = "Fine"
  62. elif multipler < 2.5:
  63. multipler_title = "Fast"
  64. elif multipler < 3.5:
  65. multipler_title = "WOW"
  66. else:
  67. multipler_title = "Insane!!"
  68. return multipler_title
  69. def formatResult(self, taken, standard):
  70. if not standard:
  71. return " Done in %.3fs" % taken
  72. if taken > 0:
  73. multipler = standard / taken
  74. else:
  75. multipler = 99
  76. multipler_title = self.getMultiplerTitle(multipler)
  77. return " Done in %.3fs = %s (%.2fx)" % (taken, multipler_title, multipler)
  78. def getBenchmarkTests(self, online=False):
  79. if hasattr(super(), "getBenchmarkTests"):
  80. tests = super().getBenchmarkTests(online)
  81. else:
  82. tests = []
  83. tests.extend([
  84. {"func": self.testHdPrivatekey, "num": 50, "time_standard": 0.57},
  85. {"func": self.testSign, "num": 20, "time_standard": 0.46},
  86. {"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto_fallback"}, "num": 20, "time_standard": 0.38},
  87. {"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto"}, "num": 200, "time_standard": 0.30},
  88. {"func": self.testVerify, "kwargs": {"lib_verify": "libsecp256k1"}, "num": 200, "time_standard": 0.10},
  89. {"func": self.testPackMsgpack, "num": 100, "time_standard": 0.35},
  90. {"func": self.testUnpackMsgpackStreaming, "kwargs": {"fallback": False}, "num": 100, "time_standard": 0.35},
  91. {"func": self.testUnpackMsgpackStreaming, "kwargs": {"fallback": True}, "num": 10, "time_standard": 0.5},
  92. {"func": self.testPackZip, "num": 5, "time_standard": 0.065},
  93. {"func": self.testPackArchive, "kwargs": {"archive_type": "gz"}, "num": 5, "time_standard": 0.08},
  94. {"func": self.testPackArchive, "kwargs": {"archive_type": "bz2"}, "num": 5, "time_standard": 0.68},
  95. {"func": self.testPackArchive, "kwargs": {"archive_type": "xz"}, "num": 5, "time_standard": 0.47},
  96. {"func": self.testUnpackZip, "num": 20, "time_standard": 0.25},
  97. {"func": self.testUnpackArchive, "kwargs": {"archive_type": "gz"}, "num": 20, "time_standard": 0.28},
  98. {"func": self.testUnpackArchive, "kwargs": {"archive_type": "bz2"}, "num": 20, "time_standard": 0.83},
  99. {"func": self.testUnpackArchive, "kwargs": {"archive_type": "xz"}, "num": 20, "time_standard": 0.38},
  100. {"func": self.testCryptHash, "kwargs": {"hash_type": "sha256"}, "num": 10, "time_standard": 0.50},
  101. {"func": self.testCryptHash, "kwargs": {"hash_type": "sha512"}, "num": 10, "time_standard": 0.33},
  102. {"func": self.testCryptHashlib, "kwargs": {"hash_type": "sha3_256"}, "num": 10, "time_standard": 0.33},
  103. {"func": self.testCryptHashlib, "kwargs": {"hash_type": "sha3_512"}, "num": 10, "time_standard": 0.65},
  104. {"func": self.testRandom, "num": 100, "time_standard": 0.08},
  105. ])
  106. if online:
  107. tests += [
  108. {"func": self.testHttps, "num": 1, "time_standard": 2.1}
  109. ]
  110. return tests
  111. def testBenchmark(self, num_multipler=1, online=False, num_run=None, filter=None):
  112. """
  113. Run benchmark on client functions
  114. """
  115. tests = self.getBenchmarkTests(online=online)
  116. if filter:
  117. tests = [test for test in tests[:] if filter.lower() in test["func"].__name__.lower()]
  118. yield "\n"
  119. res = {}
  120. res_time_taken = {}
  121. multiplers = []
  122. for test in tests:
  123. s = time.time()
  124. if num_run:
  125. num_run_test = num_run
  126. else:
  127. num_run_test = math.ceil(test["num"] * num_multipler)
  128. func = test["func"]
  129. func_name = func.__name__
  130. kwargs = test.get("kwargs", {})
  131. key = "%s %s" % (func_name, kwargs)
  132. if kwargs:
  133. yield "* Running %s (%s) x %s " % (func_name, kwargs, num_run_test)
  134. else:
  135. yield "* Running %s x %s " % (func_name, num_run_test)
  136. i = 0
  137. try:
  138. for progress in func(num_run_test, **kwargs):
  139. i += 1
  140. if num_run_test > 10:
  141. should_print = i % (num_run_test / 10) == 0 or progress != "."
  142. else:
  143. should_print = True
  144. if should_print:
  145. if num_run_test == 1 and progress == ".":
  146. progress = "..."
  147. yield progress
  148. time_taken = time.time() - s
  149. if num_run:
  150. time_standard = 0
  151. else:
  152. time_standard = test["time_standard"] * num_multipler
  153. yield self.formatResult(time_taken, time_standard)
  154. yield "\n"
  155. res[key] = "ok"
  156. res_time_taken[key] = time_taken
  157. multiplers.append(time_standard / max(time_taken, 0.001))
  158. except Exception as err:
  159. res[key] = err
  160. yield "Failed!\n! Error: %s\n\n" % Debug.formatException(err)
  161. yield "\n== Result ==\n"
  162. # Check verification speed
  163. if "testVerify {'lib_verify': 'sslcrypto'}" in res_time_taken:
  164. speed_order = ["sslcrypto_fallback", "sslcrypto", "libsecp256k1"]
  165. time_taken = {}
  166. for lib_verify in speed_order:
  167. time_taken[lib_verify] = res_time_taken["testVerify {'lib_verify': '%s'}" % lib_verify]
  168. time_taken["sslcrypto_fallback"] *= 10 # fallback benchmark only run 20 times instead of 200
  169. speedup_sslcrypto = time_taken["sslcrypto_fallback"] / time_taken["sslcrypto"]
  170. speedup_libsecp256k1 = time_taken["sslcrypto_fallback"] / time_taken["libsecp256k1"]
  171. yield "\n* Verification speedup:\n"
  172. yield " - OpenSSL: %.1fx (reference: 7.0x)\n" % speedup_sslcrypto
  173. yield " - libsecp256k1: %.1fx (reference: 23.8x)\n" % speedup_libsecp256k1
  174. if speedup_sslcrypto < 2:
  175. res["Verification speed"] = "error: OpenSSL speedup low: %.1fx" % speedup_sslcrypto
  176. if speedup_libsecp256k1 < speedup_sslcrypto:
  177. res["Verification speed"] = "error: libsecp256k1 speedup low: %.1fx" % speedup_libsecp256k1
  178. if not res:
  179. yield "! No tests found"
  180. if config.action == "test":
  181. sys.exit(1)
  182. else:
  183. num_failed = len([res_key for res_key, res_val in res.items() if res_val != "ok"])
  184. num_success = len([res_key for res_key, res_val in res.items() if res_val == "ok"])
  185. yield "\n* Tests:\n"
  186. yield " - Total: %s tests\n" % len(res)
  187. yield " - Success: %s tests\n" % num_success
  188. yield " - Failed: %s tests\n" % num_failed
  189. if any(multiplers):
  190. multipler_avg = sum(multiplers) / len(multiplers)
  191. multipler_title = self.getMultiplerTitle(multipler_avg)
  192. yield " - Average speed factor: %.2fx (%s)\n" % (multipler_avg, multipler_title)
  193. # Display errors
  194. for res_key, res_val in res.items():
  195. if res_val != "ok":
  196. yield " ! %s %s\n" % (res_key, res_val)
  197. if num_failed != 0 and config.action == "test":
  198. sys.exit(1)
  199. def testHttps(self, num_run=1):
  200. """
  201. Test https connection with valid and invalid certs
  202. """
  203. import urllib.request
  204. import urllib.error
  205. body = urllib.request.urlopen("https://google.com").read()
  206. assert len(body) > 100
  207. yield "."
  208. badssl_urls = [
  209. "https://expired.badssl.com/",
  210. "https://wrong.host.badssl.com/",
  211. "https://self-signed.badssl.com/",
  212. "https://untrusted-root.badssl.com/"
  213. ]
  214. for badssl_url in badssl_urls:
  215. try:
  216. body = urllib.request.urlopen(badssl_url).read()
  217. https_err = None
  218. except urllib.error.URLError as err:
  219. https_err = err
  220. assert https_err
  221. yield "."
  222. def testCryptHash(self, num_run=1, hash_type="sha256"):
  223. """
  224. Test hashing functions
  225. """
  226. yield "(5MB) "
  227. from Crypt import CryptHash
  228. hash_types = {
  229. "sha256": {"func": CryptHash.sha256sum, "hash_valid": "8cd629d9d6aff6590da8b80782a5046d2673d5917b99d5603c3dcb4005c45ffa"},
  230. "sha512": {"func": CryptHash.sha512sum, "hash_valid": "9ca7e855d430964d5b55b114e95c6bbb114a6d478f6485df93044d87b108904d"}
  231. }
  232. hash_func = hash_types[hash_type]["func"]
  233. hash_valid = hash_types[hash_type]["hash_valid"]
  234. data = io.BytesIO(b"Hello" * 1024 * 1024) # 5MB
  235. for i in range(num_run):
  236. data.seek(0)
  237. hash = hash_func(data)
  238. yield "."
  239. assert hash == hash_valid, "%s != %s" % (hash, hash_valid)
  240. def testCryptHashlib(self, num_run=1, hash_type="sha3_256"):
  241. """
  242. Test SHA3 hashing functions
  243. """
  244. yield "x 5MB "
  245. hash_types = {
  246. "sha3_256": {"func": hashlib.sha3_256, "hash_valid": "c8aeb3ef9fe5d6404871c0d2a4410a4d4e23268e06735648c9596f436c495f7e"},
  247. "sha3_512": {"func": hashlib.sha3_512, "hash_valid": "b75dba9472d8af3cc945ce49073f3f8214d7ac12086c0453fb08944823dee1ae83b3ffbc87a53a57cc454521d6a26fe73ff0f3be38dddf3f7de5d7692ebc7f95"},
  248. }
  249. hash_func = hash_types[hash_type]["func"]
  250. hash_valid = hash_types[hash_type]["hash_valid"]
  251. data = io.BytesIO(b"Hello" * 1024 * 1024) # 5MB
  252. for i in range(num_run):
  253. data.seek(0)
  254. h = hash_func()
  255. while 1:
  256. buff = data.read(1024 * 64)
  257. if not buff:
  258. break
  259. h.update(buff)
  260. hash = h.hexdigest()
  261. yield "."
  262. assert hash == hash_valid, "%s != %s" % (hash, hash_valid)
  263. def testRandom(self, num_run=1):
  264. """
  265. Test generating random data
  266. """
  267. yield "x 1000 x 256 bytes "
  268. for i in range(num_run):
  269. data_last = None
  270. for y in range(1000):
  271. data = os.urandom(256)
  272. assert data != data_last
  273. assert len(data) == 256
  274. data_last = data
  275. yield "."
  276. def testHdPrivatekey(self, num_run=2):
  277. """
  278. Test generating deterministic private keys from a master seed
  279. """
  280. from Crypt import CryptBitcoin
  281. seed = "e180efa477c63b0f2757eac7b1cce781877177fe0966be62754ffd4c8592ce38"
  282. privatekeys = []
  283. for i in range(num_run):
  284. privatekeys.append(CryptBitcoin.hdPrivatekey(seed, i * 10))
  285. yield "."
  286. valid = "5JSbeF5PevdrsYjunqpg7kAGbnCVYa1T4APSL3QRu8EoAmXRc7Y"
  287. assert privatekeys[0] == valid, "%s != %s" % (privatekeys[0], valid)
  288. if len(privatekeys) > 1:
  289. assert privatekeys[0] != privatekeys[-1]
  290. def testSign(self, num_run=1):
  291. """
  292. Test signing data using a private key
  293. """
  294. from Crypt import CryptBitcoin
  295. data = "Hello" * 1024
  296. privatekey = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
  297. for i in range(num_run):
  298. yield "."
  299. sign = CryptBitcoin.sign(data, privatekey)
  300. valid = "G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w="
  301. assert sign == valid, "%s != %s" % (sign, valid)
  302. def testVerify(self, num_run=1, lib_verify="sslcrypto"):
  303. """
  304. Test verification of generated signatures
  305. """
  306. from Crypt import CryptBitcoin
  307. CryptBitcoin.loadLib(lib_verify, silent=True)
  308. data = "Hello" * 1024
  309. privatekey = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
  310. address = CryptBitcoin.privatekeyToAddress(privatekey)
  311. sign = "G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w="
  312. for i in range(num_run):
  313. ok = CryptBitcoin.verify(data, address, sign, lib_verify=lib_verify)
  314. yield "."
  315. assert ok, "does not verify from %s" % address
  316. if lib_verify == "sslcrypto":
  317. yield("(%s)" % CryptBitcoin.sslcrypto.ecc.get_backend())
  318. def testPortCheckers(self):
  319. """
  320. Test all active open port checker
  321. """
  322. from Peer import PeerPortchecker
  323. for ip_type, func_names in PeerPortchecker.PeerPortchecker.checker_functions.items():
  324. yield "\n- %s:" % ip_type
  325. for func_name in func_names:
  326. yield "\n - Tracker %s: " % func_name
  327. try:
  328. for res in self.testPortChecker(func_name):
  329. yield res
  330. except Exception as err:
  331. yield Debug.formatException(err)
  332. def testPortChecker(self, func_name):
  333. """
  334. Test single open port checker
  335. """
  336. from Peer import PeerPortchecker
  337. peer_portchecker = PeerPortchecker.PeerPortchecker(None)
  338. announce_func = getattr(peer_portchecker, func_name)
  339. res = announce_func(3894)
  340. yield res
  341. def testAll(self):
  342. """
  343. Run all tests to check system compatibility with ZeroNet functions
  344. """
  345. for progress in self.testBenchmark(online=not config.offline, num_run=1):
  346. yield progress
  347. @PluginManager.registerTo("ConfigPlugin")
  348. class ConfigPlugin(object):
  349. def createArguments(self):
  350. back = super(ConfigPlugin, self).createArguments()
  351. if self.getCmdlineValue("test") == "benchmark":
  352. self.test_parser.add_argument(
  353. '--num_multipler', help='Benchmark run time multipler',
  354. default=1.0, type=float, metavar='num'
  355. )
  356. self.test_parser.add_argument(
  357. '--filter', help='Filter running benchmark',
  358. default=None, metavar='test name'
  359. )
  360. elif self.getCmdlineValue("test") == "portChecker":
  361. self.test_parser.add_argument(
  362. '--func_name', help='Name of open port checker function',
  363. default=None, metavar='func_name'
  364. )
  365. return back