From 8aba5aed4fceecaae0d5f574e51463f167fe4ddd Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 10 Nov 2024 02:12:18 +0000 Subject: [PATCH] list active downloads in controlpanel --- README.md | 1 + copyparty/__init__.py | 1 + copyparty/__main__.py | 1 + copyparty/broker_mp.py | 45 +++++++++++++-- copyparty/broker_mpw.py | 39 ++++++------- copyparty/broker_thr.py | 4 +- copyparty/httpcli.py | 115 ++++++++++++++++++++++++++++++++++++++ copyparty/httpsrv.py | 39 +++++++++++++ copyparty/metrics.py | 3 + copyparty/svchub.py | 5 +- copyparty/tcpsrv.py | 4 +- copyparty/util.py | 14 ++++- copyparty/web/iiam.gif | Bin 0 -> 230 bytes copyparty/web/splash.css | 7 +++ copyparty/web/splash.html | 12 ++++ copyparty/web/splash.js | 2 + docs/devnotes.md | 1 + scripts/sfx.ls | 1 + scripts/tl.js | 1 + tests/test_metrics.py | 1 + tests/util.py | 6 +- 21 files changed, 271 insertions(+), 31 deletions(-) create mode 100644 copyparty/web/iiam.gif diff --git a/README.md b/README.md index 4fa5ff36..1afedcf6 100644 --- a/README.md +++ b/README.md @@ -1687,6 +1687,7 @@ scrape_configs: currently the following metrics are available, * `cpp_uptime_seconds` time since last copyparty restart * `cpp_boot_unixtime_seconds` same but as an absolute timestamp +* `cpp_active_dl` number of active downloads * `cpp_http_conns` number of open http(s) connections * `cpp_http_reqs` number of http(s) requests handled * `cpp_sus_reqs` number of 403/422/malicious requests diff --git a/copyparty/__init__.py b/copyparty/__init__.py index 1bca4b9c..de2cd063 100644 --- a/copyparty/__init__.py +++ b/copyparty/__init__.py @@ -80,6 +80,7 @@ web/deps/prismd.css web/deps/scp.woff2 web/deps/sha512.ac.js web/deps/sha512.hw.js +web/iiam.gif web/md.css web/md.html web/md.js diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 19ba327c..f4d95e85 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1321,6 +1321,7 @@ def add_admin(ap): ap2.add_argument("--no-reload", action="store_true", help="disable ?reload=cfg (reload users/volumes/volflags from config file)") ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)") ap2.add_argument("--no-stack", action="store_true", help="disable ?stack (list all stacks)") + ap2.add_argument("--dl-list", metavar="LVL", type=int, default=2, help="who can see active downloads in the controlpanel? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=everyone") def add_thumbnail(ap): diff --git a/copyparty/broker_mp.py b/copyparty/broker_mp.py index 0b83946f..52c9afed 100644 --- a/copyparty/broker_mp.py +++ b/copyparty/broker_mp.py @@ -43,6 +43,9 @@ class BrokerMp(object): self.procs = [] self.mutex = threading.Lock() + self.retpend: dict[int, Any] = {} + self.retpend_mutex = threading.Lock() + self.num_workers = self.args.j or CORES self.log("broker", "booting {} subprocesses".format(self.num_workers)) for n in range(1, self.num_workers + 1): @@ -54,6 +57,8 @@ class BrokerMp(object): self.procs.append(proc) proc.start() + Daemon(self.periodic, "mp-periodic") + def shutdown(self) -> None: self.log("broker", "shutting down") for n, proc in enumerate(self.procs): @@ -90,8 +95,10 @@ class BrokerMp(object): self.log(*args) elif dest == "retq": - # response from previous ipc call - raise Exception("invalid broker_mp usage") + with self.retpend_mutex: + retq = self.retpend.pop(retq_id) + + retq.put(args[0]) else: # new ipc invoking managed service in hub @@ -109,7 +116,6 @@ class BrokerMp(object): proc.q_pend.put((retq_id, "retq", rv)) def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]: - # new non-ipc invoking managed service in hub obj = self.hub for node in dest.split("."): @@ -121,17 +127,30 @@ class BrokerMp(object): retq.put(rv) return retq + def wask(self, dest: str, *args: Any) -> list[Union[ExceptionalQueue, NotExQueue]]: + # call from hub to workers + ret = [] + for p in self.procs: + retq = ExceptionalQueue(1) + retq_id = id(retq) + with self.retpend_mutex: + self.retpend[retq_id] = retq + + p.q_pend.put((retq_id, dest, list(args))) + ret.append(retq) + return ret + def say(self, dest: str, *args: Any) -> None: """ send message to non-hub component in other process, returns a Queue object which eventually contains the response if want_retval (not-impl here since nothing uses it yet) """ - if dest == "listen": + if dest == "httpsrv.listen": for p in self.procs: p.q_pend.put((0, dest, [args[0], len(self.procs)])) - elif dest == "set_netdevs": + elif dest == "httpsrv.set_netdevs": for p in self.procs: p.q_pend.put((0, dest, list(args))) @@ -140,3 +159,19 @@ class BrokerMp(object): else: raise Exception("what is " + str(dest)) + + def periodic(self) -> None: + while True: + time.sleep(1) + + tdli = {} + tdls = {} + qs = self.wask("httpsrv.read_dls") + for q in qs: + qr = q.get() + dli, dls = qr + tdli.update(dli) + tdls.update(dls) + tdl = (tdli, tdls) + for p in self.procs: + p.q_pend.put((0, "httpsrv.write_dls", tdl)) diff --git a/copyparty/broker_mpw.py b/copyparty/broker_mpw.py index 5450050b..f1db3a29 100644 --- a/copyparty/broker_mpw.py +++ b/copyparty/broker_mpw.py @@ -82,37 +82,38 @@ class MpWorker(BrokerCli): while True: retq_id, dest, args = self.q_pend.get() - # self.logw("work: [{}]".format(d[0])) + if dest == "retq": + # response from previous ipc call + with self.retpend_mutex: + retq = self.retpend.pop(retq_id) + + retq.put(args) + continue + if dest == "shutdown": self.httpsrv.shutdown() self.logw("ok bye") sys.exit(0) return - elif dest == "reload": + if dest == "reload": self.logw("mpw.asrv reloading") self.asrv.reload() self.logw("mpw.asrv reloaded") + continue - elif dest == "reload_sessions": + if dest == "reload_sessions": with self.asrv.mutex: self.asrv.load_sessions() + continue - elif dest == "listen": - self.httpsrv.listen(args[0], args[1]) + obj = self + for node in dest.split("."): + obj = getattr(obj, node) - elif dest == "set_netdevs": - self.httpsrv.set_netdevs(args[0]) - - elif dest == "retq": - # response from previous ipc call - with self.retpend_mutex: - retq = self.retpend.pop(retq_id) - - retq.put(args) - - else: - raise Exception("what is " + str(dest)) + rv = obj(*args) # type: ignore + if retq_id: + self.say("retq", rv, retq_id=retq_id) def ask(self, dest: str, *args: Any) -> Union[ExceptionalQueue, NotExQueue]: retq = ExceptionalQueue(1) @@ -123,5 +124,5 @@ class MpWorker(BrokerCli): self.q_yield.put((retq_id, dest, list(args))) return retq - def say(self, dest: str, *args: Any) -> None: - self.q_yield.put((0, dest, list(args))) + def say(self, dest: str, *args: Any, retq_id=0) -> None: + self.q_yield.put((retq_id, dest, list(args))) diff --git a/copyparty/broker_thr.py b/copyparty/broker_thr.py index 4b021feb..43bea239 100644 --- a/copyparty/broker_thr.py +++ b/copyparty/broker_thr.py @@ -53,11 +53,11 @@ class BrokerThr(BrokerCli): return NotExQueue(obj(*args)) # type: ignore def say(self, dest: str, *args: Any) -> None: - if dest == "listen": + if dest == "httpsrv.listen": self.httpsrv.listen(args[0], 1) return - if dest == "set_netdevs": + if dest == "httpsrv.set_netdevs": self.httpsrv.set_netdevs(args[0]) return diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 43eb3d68..a97a0bde 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -186,6 +186,7 @@ class HttpCli(object): self.rem = " " self.vpath = " " self.vpaths = " " + self.dl_id = "" self.gctx = " " # additional context for garda self.trailing_slash = True self.uname = " " @@ -726,6 +727,11 @@ class HttpCli(object): except Pebkac: return False + finally: + if self.dl_id: + self.conn.hsrv.dli.pop(self.dl_id, None) + self.conn.hsrv.dls.pop(self.dl_id, None) + def dip(self) -> str: if self.args.plain_ip: return self.ip.replace(":", ".") @@ -1218,6 +1224,9 @@ class HttpCli(object): if "shares" in self.uparam: return self.tx_shares() + if "dls" in self.uparam: + return self.tx_dls() + if "h" in self.uparam: return self.tx_mounts() @@ -3690,6 +3699,8 @@ class HttpCli(object): self.args.s_wr_sz, self.args.s_wr_slp, not self.args.no_poll, + {}, + "", ) res.close() @@ -3736,6 +3747,7 @@ class HttpCli(object): editions: dict[str, tuple[str, int]] = {} for ext in ("", ".gz"): if ptop is not None: + assert job and ap_data # type: ignore # !rm sz = job["size"] file_ts = job["lmod"] editions["plain"] = (ap_data, sz) @@ -3904,7 +3916,21 @@ class HttpCli(object): self.send_headers(length=upper - lower, status=status, mime=mime) return True + dls = self.conn.hsrv.dls + if upper - lower > 0x400000: # 4m + now = time.time() + self.dl_id = "%s:%s" % (self.ip, self.addr[1]) + dls[self.dl_id] = (now, 0) + self.conn.hsrv.dli[self.dl_id] = ( + now, + upper - lower, + self.vn, + self.vpath, + self.uname, + ) + if ptop is not None: + assert job and ap_data # type: ignore # !rm return self.tx_pipe( ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg ) @@ -3923,6 +3949,8 @@ class HttpCli(object): self.args.s_wr_sz, self.args.s_wr_slp, not self.args.no_poll, + dls, + self.dl_id, ) if remains > 0: @@ -4073,6 +4101,8 @@ class HttpCli(object): wr_sz, wr_slp, not self.args.no_poll, + self.conn.hsrv.dls, + self.dl_id, ) spd = self._spd((upper - lower) - remains) @@ -4158,6 +4188,18 @@ class HttpCli(object): self.log("transcoding to [{}]".format(cfmt)) fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt) + now = time.time() + self.dl_id = "%s:%s" % (self.ip, self.addr[1]) + self.conn.hsrv.dli[self.dl_id] = ( + now, + 0, + self.vn, + "%s :%s" % (self.vpath, ext), + self.uname, + ) + dls = self.conn.hsrv.dls + dls[self.dl_id] = (time.time(), 0) + bgen = packer( self.log, self.asrv, @@ -4166,6 +4208,7 @@ class HttpCli(object): pre_crc="crc" in uarg, cmp=uarg if cancmp or uarg == "pax" else "", ) + n = 0 bsent = 0 for buf in bgen.gen(): if not buf: @@ -4179,6 +4222,11 @@ class HttpCli(object): bgen.stop() break + n += 1 + if n >= 4: + n = 0 + dls[self.dl_id] = (time.time(), bsent) + spd = self._spd(bsent) self.log("{}, {}".format(logmsg, spd)) return True @@ -4436,6 +4484,32 @@ class HttpCli(object): assert vstate.items and vs # type: ignore # !rm + dls = dl_list = [] + if self.conn.hsrv.tdls: + zi = self.args.dl_list + if zi == 2 or (zi == 1 and self.avol): + dl_list = self.get_dls() + for t0, t1, sent, sz, vp, dl_id, uname in dl_list: + rem = sz - sent + td = max(0.1, now - t0) + rd, fn = vsplit(vp) + if not rd: + rd = "/" + erd = quotep(rd) + rds = rd.replace("/", " / ") + spd = humansize(sent / td, True) + "/s" + hsent = humansize(sent, True) + idle = s2hms(now - t1, True) + usr = "%s @%s" % (dl_id, uname) if dl_id else uname + if sz and sent and td: + eta = s2hms((sz - sent) / (sent / td), True) + perc = int(100 * sent / sz) + else: + eta = perc = "--" + + fn = html_escape(fn) if fn else self.conn.hsrv.iiam + dls.append((perc, hsent, spd, eta, idle, usr, erd, rds, fn)) + fmt = self.uparam.get("ls", "") if not fmt and (self.ua.startswith("curl/") or self.ua.startswith("fetch")): fmt = "v" @@ -4457,6 +4531,12 @@ class HttpCli(object): txt += "\n%s" % (", ".join((str(x) for x in zt)),) txt += "\n" + if dls: + txt += "\n\nactive downloads:" + for zt in dls: + txt += "\n%s" % (", ".join((str(x) for x in zt)),) + txt += "\n" + if rvol: txt += "\nyou can browse:" for v in rvol: @@ -4480,6 +4560,7 @@ class HttpCli(object): avol=avol, in_shr=self.args.shr and self.vpath.startswith(self.args.shr1), vstate=vstate, + dls=dls, ups=ups, scanning=vs["scanning"], hashq=vs["hashq"], @@ -4700,6 +4781,40 @@ class HttpCli(object): ret["a"] = dirs return ret + def get_dls(self) -> list[list[Any]]: + ret = [] + dls = self.conn.hsrv.tdls + for dl_id, (t0, sz, vn, vp, uname) in self.conn.hsrv.tdli.items(): + t1, sent = dls[dl_id] + if sent > 0x100000: # 1m; buffers 2~4 + sent -= 0x100000 + if self.uname not in vn.axs.uread: + vp = "" + elif self.uname not in vn.axs.udot and (vp.startswith(".") or "/." in vp): + vp = "" + if self.uname not in vn.axs.uadmin: + dl_id = uname = "" + + ret.append([t0, t1, sent, sz, vp, dl_id, uname]) + return ret + + def tx_dls(self) -> bool: + ret = [ + { + "t0": x[0], + "t1": x[1], + "sent": x[2], + "size": x[3], + "path": x[4], + "conn": x[5], + "uname": x[6], + } + for x in self.get_dls() + ] + zs = json.dumps(ret, separators=(",\n", ": ")) + self.reply(zs.encode("utf-8", "replace"), mime="application/json") + return True + def tx_ups(self) -> bool: idx = self.conn.get_u2idx() if not idx or not hasattr(idx, "p_end"): diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 2737b841..7a3820e3 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -81,6 +81,7 @@ from .util import ( ) if TYPE_CHECKING: + from .authsrv import VFS from .broker_util import BrokerCli from .ssdp import SSDPr @@ -130,6 +131,12 @@ class HttpSrv(object): self.bans: dict[str, int] = {} self.aclose: dict[str, int] = {} + dli: dict[str, tuple[float, int, "VFS", str, str]] = {} # info + dls: dict[str, tuple[float, int]] = {} # state + self.dli = self.tdli = dli + self.dls = self.tdls = dls + self.iiam = '' % (self.args.SRS,) + self.bound: set[tuple[str, int]] = set() self.name = "hsrv" + nsuf self.mutex = threading.Lock() @@ -205,6 +212,9 @@ class HttpSrv(object): self.start_threads(4) if nid: + self.tdli = {} + self.tdls = {} + if self.args.stackmon: start_stackmon(self.args.stackmon, nid) @@ -579,3 +589,32 @@ class HttpSrv(object): ident += "a" self.u2idx_free[ident] = u2idx + + def read_dls( + self, + ) -> tuple[ + dict[str, tuple[float, int, str, str, str]], dict[str, tuple[float, int]] + ]: + """ + mp-broker asking for local dl-info + dl-state; + reduce overhead by sending just the vfs vpath + """ + dli = {k: (a, b, c.vpath, d, e) for k, (a, b, c, d, e) in self.dli.items()} + return (dli, self.dls) + + def write_dls( + self, + sdli: dict[str, tuple[float, int, str, str, str]], + dls: dict[str, tuple[float, int]], + ) -> None: + """ + mp-broker pushing total dl-info + dl-state; + swap out the vfs vpath with the vfs node + """ + dli: dict[str, tuple[float, int, "VFS", str, str]] = {} + for k, (a, b, c, d, e) in sdli.items(): + vn = self.asrv.vfs.all_vols[c] + dli[k] = (a, b, vn, d, e) + + self.tdli = dli + self.tdls = dls diff --git a/copyparty/metrics.py b/copyparty/metrics.py index af54c93f..26413147 100644 --- a/copyparty/metrics.py +++ b/copyparty/metrics.py @@ -72,6 +72,9 @@ class Metrics(object): v = "{:.3f}".format(self.hsrv.t0) addug("cpp_boot_unixtime", "seconds", v, t) + t = "number of active downloads" + addg("cpp_active_dl", str(len(self.hsrv.tdls)), t) + t = "number of open http(s) client connections" addg("cpp_http_conns", str(self.hsrv.ncli), t) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index cf5db4a1..20cb7bb3 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -1004,15 +1004,18 @@ class SvcHub(object): except: self.log("root", "ssdp startup failed;\n" + min_ex(), 3) - def reload(self, rescan_all_vols: bool, up2k: bool) -> None: + def reload(self, rescan_all_vols: bool, up2k: bool) -> str: + t = "config has been reloaded" with self.reload_mutex: self.log("root", "reloading config") self.asrv.reload(9 if up2k else 4) if up2k: self.up2k.reload(rescan_all_vols) + t += "; volumes are now reinitializing" else: self.log("root", "reload done") self.broker.reload() + return t def _reload_sessions(self) -> None: with self.asrv.mutex: diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 88aa654a..23121793 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -371,7 +371,7 @@ class TcpSrv(object): if self.args.q: print(msg) - self.hub.broker.say("listen", srv) + self.hub.broker.say("httpsrv.listen", srv) self.srv = srvs self.bound = bound @@ -379,7 +379,7 @@ class TcpSrv(object): self._distribute_netdevs() def _distribute_netdevs(self): - self.hub.broker.say("set_netdevs", self.netdevs) + self.hub.broker.say("httpsrv.set_netdevs", self.netdevs) self.hub.start_zeroconf() gencert(self.log, self.args, self.netdevs) self.hub.restart_ftpd() diff --git a/copyparty/util.py b/copyparty/util.py index 1fe1a708..260250fc 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -2791,7 +2791,10 @@ def sendfile_py( bufsz: int, slp: float, use_poll: bool, + dls: dict[str, tuple[float, int]], + dl_id: str, ) -> int: + sent = 0 remains = upper - lower f.seek(lower) while remains > 0: @@ -2808,6 +2811,10 @@ def sendfile_py( except: return remains + if dl_id: + sent += len(buf) + dls[dl_id] = (time.time(), sent) + return 0 @@ -2820,6 +2827,8 @@ def sendfile_kern( bufsz: int, slp: float, use_poll: bool, + dls: dict[str, tuple[float, int]], + dl_id: str, ) -> int: out_fd = s.fileno() in_fd = f.fileno() @@ -2832,7 +2841,7 @@ def sendfile_kern( while ofs < upper: stuck = stuck or time.time() try: - req = min(2 ** 30, upper - ofs) + req = min(0x2000000, upper - ofs) # 32 MiB if use_poll: poll.poll(10000) else: @@ -2856,6 +2865,9 @@ def sendfile_kern( return upper - ofs ofs += n + if dl_id: + dls[dl_id] = (time.time(), ofs - lower) + # print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs)) return 0 diff --git a/copyparty/web/iiam.gif b/copyparty/web/iiam.gif new file mode 100644 index 0000000000000000000000000000000000000000..a57c10cc58de9c4c92320e7c99f368b0698a91e1 GIT binary patch literal 230 zcmVRNzP^o8oC67zM64RIfFcgNElTHo?+Q@>x_m)Q$#p$b_-l z&~(zB44+Waip?@}#*TM!OuCekG0llkc{$xvr)rC5QtId%443O4)%v=>7dYbgCTLhf zICZ#qk(kHGwCDE-$w>8eHs;g=p;@yT6DrDwNda*x%Bh8Frb^0oA;T&rTlP6S1RIMw gYKB#t)if7oDyHQt+|){zOS~7Ux3y4dIZ*%rI|q+rod5s; literal 0 HcmV?d00001 diff --git a/copyparty/web/splash.css b/copyparty/web/splash.css index 144886b9..d333d027 100644 --- a/copyparty/web/splash.css +++ b/copyparty/web/splash.css @@ -90,6 +90,10 @@ table { text-align: left; white-space: nowrap; } +.vols td:empty, +.vols th:empty { + padding: 0; +} .num { border-right: 1px solid #bbb; } @@ -222,3 +226,6 @@ html.bz { color: #bbd; background: #11121d; } +html.bz .vols img { + filter: sepia(0.8) hue-rotate(180deg); +} diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index d9cd7a39..1401ac8b 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -44,6 +44,18 @@ {%- endif %} + {%- if dls %} +

active downloads:

+ + + + {% for u in dls %} + + {% endfor %} + +
%sentspeedetaidledirfile
{{ u[0] }}{{ u[1] }}{{ u[2] }}{{ u[3] }}{{ u[4] }}{{ u[5] }}{{ u[7]|e }}{{ u[8] }}
+ {%- endif %} + {%- if avol %}

admin panel:

diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index ebeed1bd..07a27e77 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -37,6 +37,7 @@ var Ls = { "ab1": "skru av no304", "ac1": "skru på no304", "ad1": "no304 stopper all bruk av cache. Hvis ikke k304 var nok, prøv denne. Vil mangedoble dataforbruk!", + "ae1": "utgående:", }, "eng": { "d2": "shows the state of all active threads", @@ -86,6 +87,7 @@ var Ls = { "ab1": "关闭 k304", "ac1": "开启 k304", "ad1": "启用 no304 将禁用所有缓存;如果 k304 不够,可以尝试此选项。这将消耗大量的网络流量!", //m + "ae1": "正在下载:", //m } }; diff --git a/docs/devnotes.md b/docs/devnotes.md index 6d39704a..f9cf9b72 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -140,6 +140,7 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo` | GET | `?tar&j` | pregenerate jpg thumbnails | | GET | `?tar&p` | pregenerate audio waveforms | | GET | `?shares` | list your shared files/folders | +| GET | `?dls` | show active downloads (do this as admin) | | GET | `?ups` | show recent uploads from your IP | | GET | `?ups&filter=f` | ...where URL contains `f` | | GET | `?mime=foo` | specify return mimetype `foo` | diff --git a/scripts/sfx.ls b/scripts/sfx.ls index 46d5eb40..b4e04677 100644 --- a/scripts/sfx.ls +++ b/scripts/sfx.ls @@ -94,6 +94,7 @@ copyparty/web/deps/prismd.css, copyparty/web/deps/scp.woff2, copyparty/web/deps/sha512.ac.js, copyparty/web/deps/sha512.hw.js, +copyparty/web/iiam.gif, copyparty/web/md.css, copyparty/web/md.html, copyparty/web/md.js, diff --git a/scripts/tl.js b/scripts/tl.js index 22cde3fa..e88d71ff 100644 --- a/scripts/tl.js +++ b/scripts/tl.js @@ -79,6 +79,7 @@ var tl_cpanel = { "ab1": "disable no304", "ac1": "enable no304", "ad1": "enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!", + "ae1": "active downloads:", }, }; diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 194fa978..16bdae06 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -57,6 +57,7 @@ class TestMetrics(unittest.TestCase): ptns = r""" cpp_uptime_seconds [0-9]\.[0-9]{3}$ cpp_boot_unixtime_seconds [0-9]{7,10}\.[0-9]{3}$ +cpp_active_dl 0$ cpp_http_reqs_created [0-9]{7,10}$ cpp_http_reqs_total -1$ cpp_http_conns 9$ diff --git a/tests/util.py b/tests/util.py index 4b72c65d..a133ae55 100644 --- a/tests/util.py +++ b/tests/util.py @@ -134,7 +134,7 @@ class Cfg(Namespace): ex = "hash_mt safe_dedup srch_time u2abort u2j u2sz" ka.update(**{k: 1 for k in ex.split()}) - ex = "au_vol mtab_age reg_cap s_thead s_tbody th_convt" + ex = "au_vol dl_list mtab_age reg_cap s_thead s_tbody th_convt" ka.update(**{k: 9 for k in ex.split()}) ex = "db_act k304 loris no304 re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo" @@ -254,6 +254,8 @@ class VHttpSrv(object): self.broker = NullBroker(args, asrv) self.prism = None self.bans = {} + self.tdls = self.dls = {} + self.tdli = self.dli = {} self.nreq = 0 self.nsus = 0 @@ -292,6 +294,8 @@ class VHttpConn(object): self.args = args self.asrv = asrv self.bans = {} + self.tdls = self.dls = {} + self.tdli = self.dli = {} self.freshen_pwd = 0.0 Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv