Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 627 lines 26 kB view raw
1import time 2import html 3import os 4import json 5import sys 6import itertools 7 8from Plugin import PluginManager 9from Config import config 10from util import helper 11from Debug import Debug 12from Db import Db 13 14 15@PluginManager.registerTo("UiRequest") 16class UiRequestPlugin(object): 17 18 def formatTableRow(self, row, class_name=""): 19 back = [] 20 for format, val in row: 21 if val is None: 22 formatted = "n/a" 23 elif format == "since": 24 if val: 25 formatted = "%.0f" % (time.time() - val) 26 else: 27 formatted = "n/a" 28 else: 29 formatted = format % val 30 back.append("<td>%s</td>" % formatted) 31 return "<tr class='%s'>%s</tr>" % (class_name, "".join(back)) 32 33 def getObjSize(self, obj, hpy=None): 34 if hpy: 35 return float(hpy.iso(obj).domisize) / 1024 36 else: 37 return 0 38 39 def renderHead(self): 40 import main 41 from Crypt import CryptConnection 42 43 # Memory 44 yield "rev%s | " % config.rev 45 yield "%s | " % main.file_server.ip_external_list 46 yield "Port: %s | " % main.file_server.port 47 yield "Network: %s | " % main.file_server.supported_ip_types 48 yield "Opened: %s | " % main.file_server.port_opened 49 yield "Crypt: %s, TLSv1.3: %s | " % (CryptConnection.manager.crypt_supported, CryptConnection.ssl.HAS_TLSv1_3) 50 yield "In: %.2fMB, Out: %.2fMB | " % ( 51 float(main.file_server.bytes_recv) / 1024 / 1024, 52 float(main.file_server.bytes_sent) / 1024 / 1024 53 ) 54 yield "Peerid: %s | " % main.file_server.peer_id 55 yield "Time: %.2fs | " % main.file_server.getTimecorrection() 56 yield "Blocks: %s" % Debug.num_block 57 58 try: 59 import psutil 60 process = psutil.Process(os.getpid()) 61 mem = process.get_memory_info()[0] / float(2 ** 20) 62 yield "Mem: %.2fMB | " % mem 63 yield "Threads: %s | " % len(process.threads()) 64 yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times() 65 yield "Files: %s | " % len(process.open_files()) 66 yield "Sockets: %s | " % len(process.connections()) 67 yield "Calc size <a href='?size=1'>on</a> <a href='?size=0'>off</a>" 68 except Exception: 69 pass 70 yield "<br>" 71 72 def renderConnectionsTable(self): 73 import main 74 75 # Connections 76 yield "<b>Connections</b> (%s, total made: %s, in: %s, out: %s):<br>" % ( 77 len(main.file_server.connections), main.file_server.last_connection_id, 78 main.file_server.num_incoming, main.file_server.num_outgoing 79 ) 80 yield "<table class='connections'><tr> <th>id</th> <th>type</th> <th>ip</th> <th>open</th> <th>crypt</th> <th>ping</th>" 81 yield "<th>buff</th> <th>bad</th> <th>idle</th> <th>open</th> <th>delay</th> <th>cpu</th> <th>out</th> <th>in</th> <th>last sent</th>" 82 yield "<th>wait</th> <th>version</th> <th>time</th> <th>sites</th> </tr>" 83 for connection in main.file_server.connections: 84 if "cipher" in dir(connection.sock): 85 cipher = connection.sock.cipher()[0] 86 tls_version = connection.sock.version() 87 else: 88 cipher = connection.crypt 89 tls_version = "" 90 if "time" in connection.handshake and connection.last_ping_delay: 91 time_correction = connection.handshake["time"] - connection.handshake_time - connection.last_ping_delay 92 else: 93 time_correction = 0.0 94 yield self.formatTableRow([ 95 ("%3d", connection.id), 96 ("%s", connection.type), 97 ("%s:%s", (connection.ip, connection.port)), 98 ("%s", connection.handshake.get("port_opened")), 99 ("<span title='%s %s'>%s</span>", (cipher, tls_version, connection.crypt)), 100 ("%6.3f", connection.last_ping_delay), 101 ("%s", connection.incomplete_buff_recv), 102 ("%s", connection.bad_actions), 103 ("since", max(connection.last_send_time, connection.last_recv_time)), 104 ("since", connection.start_time), 105 ("%.3f", max(-1, connection.last_sent_time - connection.last_send_time)), 106 ("%.3f", connection.cpu_time), 107 ("%.0fk", connection.bytes_sent / 1024), 108 ("%.0fk", connection.bytes_recv / 1024), 109 ("<span title='Recv: %s'>%s</span>", (connection.last_cmd_recv, connection.last_cmd_sent)), 110 ("%s", list(connection.waiting_requests.keys())), 111 ("%s r%s", (connection.handshake.get("version"), connection.handshake.get("rev", "?"))), 112 ("%.2fs", time_correction), 113 ("%s", connection.sites) 114 ]) 115 yield "</table>" 116 117 def renderTrackers(self): 118 # Trackers 119 yield "<br><br><b>Trackers:</b><br>" 120 yield "<table class='trackers'><tr> <th>address</th> <th>request</th> <th>successive errors</th> <th>last_request</th></tr>" 121 from Site import SiteAnnouncer # importing at the top of the file breaks plugins 122 for tracker_address, tracker_stat in sorted(SiteAnnouncer.global_stats.items()): 123 yield self.formatTableRow([ 124 ("%s", tracker_address), 125 ("%s", tracker_stat["num_request"]), 126 ("%s", tracker_stat["num_error"]), 127 ("%.0f min ago", min(999, (time.time() - tracker_stat["time_request"]) / 60)) 128 ]) 129 yield "</table>" 130 131 if "AnnounceShare" in PluginManager.plugin_manager.plugin_names: 132 yield "<br><br><b>Shared trackers:</b><br>" 133 yield "<table class='trackers'><tr> <th>address</th> <th>added</th> <th>found</th> <th>latency</th> <th>successive errors</th> <th>last_success</th></tr>" 134 from AnnounceShare import AnnounceSharePlugin 135 for tracker_address, tracker_stat in sorted(AnnounceSharePlugin.tracker_storage.getTrackers().items()): 136 yield self.formatTableRow([ 137 ("%s", tracker_address), 138 ("%.0f min ago", min(999, (time.time() - tracker_stat["time_added"]) / 60)), 139 ("%.0f min ago", min(999, (time.time() - tracker_stat.get("time_found", 0)) / 60)), 140 ("%.3fs", tracker_stat["latency"]), 141 ("%s", tracker_stat["num_error"]), 142 ("%.0f min ago", min(999, (time.time() - tracker_stat["time_success"]) / 60)), 143 ]) 144 yield "</table>" 145 146 def renderTor(self): 147 import main 148 yield "<br><br><b>Tor hidden services (status: %s):</b><br>" % main.file_server.tor_manager.status 149 for site_address, onion in list(main.file_server.tor_manager.site_onions.items()): 150 yield "- %-34s: %s<br>" % (site_address, onion) 151 152 def renderDbStats(self): 153 yield "<br><br><b>Db</b>:<br>" 154 for db in Db.opened_dbs: 155 tables = [row["name"] for row in db.execute("SELECT name FROM sqlite_master WHERE type = 'table'").fetchall()] 156 table_rows = {} 157 for table in tables: 158 table_rows[table] = db.execute("SELECT COUNT(*) AS c FROM %s" % table).fetchone()["c"] 159 db_size = os.path.getsize(db.db_path) / 1024.0 / 1024.0 160 yield "- %.3fs: %s %.3fMB, table rows: %s<br>" % ( 161 time.time() - db.last_query_time, db.db_path, db_size, json.dumps(table_rows, sort_keys=True) 162 ) 163 164 def renderSites(self): 165 yield "<br><br><b>Sites</b>:" 166 yield "<table>" 167 yield "<tr><th>address</th> <th>connected</th> <th title='connected/good/total'>peers</th> <th>content.json</th> <th>out</th> <th>in</th> </tr>" 168 for site in list(self.server.sites.values()): 169 yield self.formatTableRow([ 170 ( 171 """<a href='#' onclick='document.getElementById("peers_%s").style.display="initial"; return false'>%s</a>""", 172 (site.address, site.address) 173 ), 174 ("%s", [peer.connection.id for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]), 175 ("%s/%s/%s", ( 176 len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]), 177 len(site.getConnectablePeers(100)), 178 len(site.peers) 179 )), 180 ("%s (loaded: %s)", ( 181 len(site.content_manager.contents), 182 len([key for key, val in dict(site.content_manager.contents).items() if val]) 183 )), 184 ("%.0fk", site.settings.get("bytes_sent", 0) / 1024), 185 ("%.0fk", site.settings.get("bytes_recv", 0) / 1024), 186 ], "serving-%s" % site.settings["serving"]) 187 yield "<tr><td id='peers_%s' style='display: none; white-space: pre' colspan=6>" % site.address 188 for key, peer in list(site.peers.items()): 189 if peer.time_found: 190 time_found = int(time.time() - peer.time_found) / 60 191 else: 192 time_found = "--" 193 if peer.connection: 194 connection_id = peer.connection.id 195 else: 196 connection_id = None 197 if site.content_manager.has_optional_files: 198 yield "Optional files: %4s " % len(peer.hashfield) 199 time_added = (time.time() - peer.time_added) / (60 * 60 * 24) 200 yield "(#%4s, rep: %2s, err: %s, found: %.1fs min, add: %.1f day) %30s -<br>" % (connection_id, peer.reputation, peer.connection_error, time_found, time_added, key) 201 yield "<br></td></tr>" 202 yield "</table>" 203 204 def renderBigfiles(self): 205 yield "<br><br><b>Big files</b>:<br>" 206 for site in list(self.server.sites.values()): 207 if not site.settings.get("has_bigfile"): 208 continue 209 bigfiles = {} 210 yield """<a href="#" onclick='document.getElementById("bigfiles_%s").style.display="initial"; return false'>%s</a><br>""" % (site.address, site.address) 211 for peer in list(site.peers.values()): 212 if not peer.time_piecefields_updated: 213 continue 214 for sha512, piecefield in peer.piecefields.items(): 215 if sha512 not in bigfiles: 216 bigfiles[sha512] = [] 217 bigfiles[sha512].append(peer) 218 219 yield "<div id='bigfiles_%s' style='display: none'>" % site.address 220 for sha512, peers in bigfiles.items(): 221 yield "<br> - " + sha512 + " (hash id: %s)<br>" % site.content_manager.hashfield.getHashId(sha512) 222 yield "<table>" 223 for peer in peers: 224 yield "<tr><td>" + peer.key + "</td><td>" + peer.piecefields[sha512].tostring() + "</td></tr>" 225 yield "</table>" 226 yield "</div>" 227 228 def renderRequests(self): 229 import main 230 yield "<div style='float: left'>" 231 yield "<br><br><b>Sent commands</b>:<br>" 232 yield "<table>" 233 for stat_key, stat in sorted(main.file_server.stat_sent.items(), key=lambda i: i[1]["bytes"], reverse=True): 234 yield "<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>" % (stat_key, stat["num"], stat["bytes"] / 1024) 235 yield "</table>" 236 yield "</div>" 237 238 yield "<div style='float: left; margin-left: 20%; max-width: 50%'>" 239 yield "<br><br><b>Received commands</b>:<br>" 240 yield "<table>" 241 for stat_key, stat in sorted(main.file_server.stat_recv.items(), key=lambda i: i[1]["bytes"], reverse=True): 242 yield "<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>" % (stat_key, stat["num"], stat["bytes"] / 1024) 243 yield "</table>" 244 yield "</div>" 245 yield "<div style='clear: both'></div>" 246 247 def renderMemory(self): 248 import gc 249 from Ui import UiRequest 250 251 hpy = None 252 if self.get.get("size") == "1": # Calc obj size 253 try: 254 import guppy 255 hpy = guppy.hpy() 256 except Exception: 257 pass 258 self.sendHeader() 259 260 # Object types 261 262 obj_count = {} 263 for obj in gc.get_objects(): 264 obj_type = str(type(obj)) 265 if obj_type not in obj_count: 266 obj_count[obj_type] = [0, 0] 267 obj_count[obj_type][0] += 1 # Count 268 obj_count[obj_type][1] += float(sys.getsizeof(obj)) / 1024 # Size 269 270 yield "<br><br><b>Objects in memory (types: %s, total: %s, %.2fkb):</b><br>" % ( 271 len(obj_count), 272 sum([stat[0] for stat in list(obj_count.values())]), 273 sum([stat[1] for stat in list(obj_count.values())]) 274 ) 275 276 for obj, stat in sorted(list(obj_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count 277 yield " - %.1fkb = %s x <a href=\"/Listobj?type=%s\">%s</a><br>" % (stat[1], stat[0], obj, html.escape(obj)) 278 279 # Classes 280 281 class_count = {} 282 for obj in gc.get_objects(): 283 obj_type = str(type(obj)) 284 if obj_type != "<type 'instance'>": 285 continue 286 class_name = obj.__class__.__name__ 287 if class_name not in class_count: 288 class_count[class_name] = [0, 0] 289 class_count[class_name][0] += 1 # Count 290 class_count[class_name][1] += float(sys.getsizeof(obj)) / 1024 # Size 291 292 yield "<br><br><b>Classes in memory (types: %s, total: %s, %.2fkb):</b><br>" % ( 293 len(class_count), 294 sum([stat[0] for stat in list(class_count.values())]), 295 sum([stat[1] for stat in list(class_count.values())]) 296 ) 297 298 for obj, stat in sorted(list(class_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count 299 yield " - %.1fkb = %s x <a href=\"/Dumpobj?class=%s\">%s</a><br>" % (stat[1], stat[0], obj, html.escape(obj)) 300 301 from greenlet import greenlet 302 objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)] 303 yield "<br>Greenlets (%s):<br>" % len(objs) 304 for obj in objs: 305 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 306 307 from Worker import Worker 308 objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)] 309 yield "<br>Workers (%s):<br>" % len(objs) 310 for obj in objs: 311 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 312 313 from Connection import Connection 314 objs = [obj for obj in gc.get_objects() if isinstance(obj, Connection)] 315 yield "<br>Connections (%s):<br>" % len(objs) 316 for obj in objs: 317 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 318 319 from socket import socket 320 objs = [obj for obj in gc.get_objects() if isinstance(obj, socket)] 321 yield "<br>Sockets (%s):<br>" % len(objs) 322 for obj in objs: 323 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 324 325 from msgpack import Unpacker 326 objs = [obj for obj in gc.get_objects() if isinstance(obj, Unpacker)] 327 yield "<br>Msgpack unpacker (%s):<br>" % len(objs) 328 for obj in objs: 329 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 330 331 from Site.Site import Site 332 objs = [obj for obj in gc.get_objects() if isinstance(obj, Site)] 333 yield "<br>Sites (%s):<br>" % len(objs) 334 for obj in objs: 335 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 336 337 objs = [obj for obj in gc.get_objects() if isinstance(obj, self.server.log.__class__)] 338 yield "<br>Loggers (%s):<br>" % len(objs) 339 for obj in objs: 340 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj.name))) 341 342 objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)] 343 yield "<br>UiRequests (%s):<br>" % len(objs) 344 for obj in objs: 345 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 346 347 from Peer import Peer 348 objs = [obj for obj in gc.get_objects() if isinstance(obj, Peer)] 349 yield "<br>Peers (%s):<br>" % len(objs) 350 for obj in objs: 351 yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), html.escape(repr(obj))) 352 353 objs = [(key, val) for key, val in sys.modules.items() if val is not None] 354 objs.sort() 355 yield "<br>Modules (%s):<br>" % len(objs) 356 for module_name, module in objs: 357 yield " - %.3fkb: %s %s<br>" % (self.getObjSize(module, hpy), module_name, html.escape(repr(module))) 358 359 # /Stats entry point 360 @helper.encodeResponse 361 def actionStats(self): 362 import gc 363 364 self.sendHeader() 365 366 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local: 367 yield "This function is disabled on this proxy" 368 return 369 370 s = time.time() 371 372 # Style 373 yield """ 374 <style> 375 * { font-family: monospace } 376 table td, table th { text-align: right; padding: 0px 10px } 377 .connections td { white-space: nowrap } 378 .serving-False { opacity: 0.3 } 379 </style> 380 """ 381 382 renderers = [ 383 self.renderHead(), 384 self.renderConnectionsTable(), 385 self.renderTrackers(), 386 self.renderTor(), 387 self.renderDbStats(), 388 self.renderSites(), 389 self.renderBigfiles(), 390 self.renderRequests() 391 392 ] 393 394 for part in itertools.chain(*renderers): 395 yield part 396 397 if config.debug: 398 for part in self.renderMemory(): 399 yield part 400 401 gc.collect() # Implicit grabage collection 402 yield "Done in %.1f" % (time.time() - s) 403 404 @helper.encodeResponse 405 def actionDumpobj(self): 406 407 import gc 408 import sys 409 410 self.sendHeader() 411 412 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local: 413 yield "This function is disabled on this proxy" 414 return 415 416 # No more if not in debug mode 417 if not config.debug: 418 yield "Not in debug mode" 419 return 420 421 class_filter = self.get.get("class") 422 423 yield """ 424 <style> 425 * { font-family: monospace; white-space: pre } 426 table * { text-align: right; padding: 0px 10px } 427 </style> 428 """ 429 430 objs = gc.get_objects() 431 for obj in objs: 432 obj_type = str(type(obj)) 433 if obj_type != "<type 'instance'>" or obj.__class__.__name__ != class_filter: 434 continue 435 yield "%.1fkb %s... " % (float(sys.getsizeof(obj)) / 1024, html.escape(str(obj))) 436 for attr in dir(obj): 437 yield "- %s: %s<br>" % (attr, html.escape(str(getattr(obj, attr)))) 438 yield "<br>" 439 440 gc.collect() # Implicit grabage collection 441 442 @helper.encodeResponse 443 def actionListobj(self): 444 445 import gc 446 import sys 447 448 self.sendHeader() 449 450 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local: 451 yield "This function is disabled on this proxy" 452 return 453 454 # No more if not in debug mode 455 if not config.debug: 456 yield "Not in debug mode" 457 return 458 459 type_filter = self.get.get("type") 460 461 yield """ 462 <style> 463 * { font-family: monospace; white-space: pre } 464 table * { text-align: right; padding: 0px 10px } 465 </style> 466 """ 467 468 yield "Listing all %s objects in memory...<br>" % html.escape(type_filter) 469 470 ref_count = {} 471 objs = gc.get_objects() 472 for obj in objs: 473 obj_type = str(type(obj)) 474 if obj_type != type_filter: 475 continue 476 refs = [ 477 ref for ref in gc.get_referrers(obj) 478 if hasattr(ref, "__class__") and 479 ref.__class__.__name__ not in ["list", "dict", "function", "type", "frame", "WeakSet", "tuple"] 480 ] 481 if not refs: 482 continue 483 try: 484 yield "%.1fkb <span title=\"%s\">%s</span>... " % ( 485 float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)), html.escape(str(obj)[0:100].ljust(100)) 486 ) 487 except Exception: 488 continue 489 for ref in refs: 490 yield " [" 491 if "object at" in str(ref) or len(str(ref)) > 100: 492 yield str(ref.__class__.__name__) 493 else: 494 yield str(ref.__class__.__name__) + ":" + html.escape(str(ref)) 495 yield "] " 496 ref_type = ref.__class__.__name__ 497 if ref_type not in ref_count: 498 ref_count[ref_type] = [0, 0] 499 ref_count[ref_type][0] += 1 # Count 500 ref_count[ref_type][1] += float(sys.getsizeof(obj)) / 1024 # Size 501 yield "<br>" 502 503 yield "<br>Object referrer (total: %s, %.2fkb):<br>" % (len(ref_count), sum([stat[1] for stat in list(ref_count.values())])) 504 505 for obj, stat in sorted(list(ref_count.items()), key=lambda x: x[1][0], reverse=True)[0:30]: # Sorted by count 506 yield " - %.1fkb = %s x %s<br>" % (stat[1], stat[0], html.escape(str(obj))) 507 508 gc.collect() # Implicit grabage collection 509 510 @helper.encodeResponse 511 def actionGcCollect(self): 512 import gc 513 self.sendHeader() 514 yield str(gc.collect()) 515 516 # /About entry point 517 @helper.encodeResponse 518 def actionEnv(self): 519 import main 520 521 self.sendHeader() 522 523 yield """ 524 <style> 525 * { font-family: monospace; white-space: pre; } 526 h2 { font-size: 100%; margin-bottom: 0px; } 527 small { opacity: 0.5; } 528 table { border-collapse: collapse; } 529 td { padding-right: 10px; } 530 </style> 531 """ 532 533 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local: 534 yield "This function is disabled on this proxy" 535 return 536 537 yield from main.actions.testEnv(format="html") 538 539 540@PluginManager.registerTo("Actions") 541class ActionsPlugin: 542 def formatTable(self, *rows, format="text"): 543 if format == "html": 544 return self.formatTableHtml(*rows) 545 else: 546 return self.formatTableText(*rows) 547 548 def formatHead(self, title, format="text"): 549 if format == "html": 550 return "<h2>%s</h2>" % title 551 else: 552 return "\n* %s\n" % title 553 554 def formatTableHtml(self, *rows): 555 yield "<table>" 556 for row in rows: 557 yield "<tr>" 558 for col in row: 559 yield "<td>%s</td>" % html.escape(str(col)) 560 yield "</tr>" 561 yield "</table>" 562 563 def formatTableText(self, *rows): 564 for row in rows: 565 yield " " 566 for col in row: 567 yield " " + str(col) 568 yield "\n" 569 570 def testEnv(self, format="text"): 571 import gevent 572 import msgpack 573 import pkg_resources 574 import importlib 575 import coincurve 576 import sqlite3 577 from Crypt import CryptBitcoin 578 579 yield "\n" 580 581 yield from self.formatTable( 582 ["ZeroNet version:", "%s rev%s" % (config.version, config.rev)], 583 ["Python:", "%s" % sys.version], 584 ["Platform:", "%s" % sys.platform], 585 ["Crypt verify lib:", "%s" % CryptBitcoin.lib_verify_best], 586 ["OpenSSL:", "%s" % CryptBitcoin.sslcrypto.ecc.get_backend()], 587 ["Libsecp256k1:", "%s" % type(coincurve._libsecp256k1.lib).__name__], 588 ["SQLite:", "%s, API: %s" % (sqlite3.sqlite_version, sqlite3.version)], 589 format=format 590 ) 591 592 593 yield self.formatHead("Libraries:") 594 rows = [] 595 for lib_name in ["gevent", "greenlet", "msgpack", "base58", "merkletools", "rsa", "socks", "pyasn1", "gevent_ws", "websocket", "maxminddb"]: 596 try: 597 module = importlib.import_module(lib_name) 598 if "__version__" in dir(module): 599 version = module.__version__ 600 elif "version" in dir(module): 601 version = module.version 602 else: 603 version = "unknown version" 604 605 if type(version) is tuple: 606 version = ".".join(map(str, version)) 607 608 rows.append(["- %s:" % lib_name, version, "at " + module.__file__]) 609 except Exception as err: 610 rows.append(["! Error importing %s:", repr(err)]) 611 612 """ 613 try: 614 yield " - %s<br>" % html.escape(repr(pkg_resources.get_distribution(lib_name))) 615 except Exception as err: 616 yield " ! %s<br>" % html.escape(repr(err)) 617 """ 618 619 yield from self.formatTable(*rows, format=format) 620 621 yield self.formatHead("Library config:", format=format) 622 623 yield from self.formatTable( 624 ["- gevent:", gevent.config.loop.__module__], 625 ["- msgpack unpacker:", msgpack.Unpacker.__module__], 626 format=format 627 )