diff --git a/copyparty/__main__.py b/copyparty/__main__.py index caa77a94..6a3ba91b 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -23,7 +23,7 @@ from textwrap import dedent from .__init__ import E, WINDOWS, VT100, PY2 from .__version__ import S_VERSION, S_BUILD_DT, CODENAME from .svchub import SvcHub -from .util import py_desc, align_tab, IMPLICATIONS +from .util import py_desc, align_tab, IMPLICATIONS, alltrace HAVE_SSL = True try: @@ -182,6 +182,16 @@ def sighandler(sig=None, frame=None): print("\n".join(msg)) +def stackmon(fp, ival): + ctr = 0 + while True: + ctr += 1 + time.sleep(ival) + st = "{}, {}\n{}".format(ctr, time.time(), alltrace()) + with open(fp, "wb") as f: + f.write(st.encode("utf-8", "replace")) + + def run_argparse(argv, formatter): ap = argparse.ArgumentParser( formatter_class=formatter, @@ -320,6 +330,7 @@ def run_argparse(argv, formatter): ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile") ap2.add_argument("--no-scandir", action="store_true", help="disable scandir") ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing") + ap2.add_argument("--stackmon", metavar="P,S", help="write stacktrace to Path every S second") return ap.parse_args(args=argv[1:]) # fmt: on @@ -359,6 +370,16 @@ def main(argv=None): except AssertionError: al = run_argparse(argv, Dodge11874) + if al.stackmon: + fp, f = al.stackmon.rsplit(",", 1) + f = int(f) + t = threading.Thread( + target=stackmon, + args=(fp, f), + ) + t.daemon = True + t.start() + # propagate implications for k1, k2 in IMPLICATIONS: if getattr(al, k1): diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index bb4ddaaa..abef296f 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -1434,33 +1434,8 @@ class HttpCli(object): if self.args.no_stack: raise Pebkac(403, "disabled by argv") - threads = {} - names = dict([(t.ident, t.name) for t in threading.enumerate()]) - for tid, stack in sys._current_frames().items(): - name = "{} ({:x})".format(names.get(tid), tid) - threads[name] = stack - - rret = [] - bret = [] - for name, stack in sorted(threads.items()): - ret = ["\n\n# {}".format(name)] - pad = None - for fn, lno, name, line in traceback.extract_stack(stack): - fn = os.sep.join(fn.split(os.sep)[-3:]) - ret.append('File: "{}", line {}, in {}'.format(fn, lno, name)) - if line: - ret.append(" " + str(line.strip())) - if "self.not_empty.wait()" in line: - pad = " " * 4 - - if pad: - bret += [ret[0]] + [pad + x for x in ret[1:]] - else: - rret += ret - - ret = rret + bret - ret = ("
" + "\n".join(ret)).encode("utf-8")
-        self.reply(ret)
+        ret = "
{}\n{}".format(time.time(), alltrace())
+        self.reply(ret.encode("utf-8"))
 
     def tx_tree(self):
         top = self.uparam["tree"] or ""
diff --git a/copyparty/util.py b/copyparty/util.py
index fb71bdc9..4a2308ec 100644
--- a/copyparty/util.py
+++ b/copyparty/util.py
@@ -254,6 +254,34 @@ def trace(*args, **kwargs):
     nuprint(msg)
 
 
+def alltrace():
+    threads = {}
+    names = dict([(t.ident, t.name) for t in threading.enumerate()])
+    for tid, stack in sys._current_frames().items():
+        name = "{} ({:x})".format(names.get(tid), tid)
+        threads[name] = stack
+
+    rret = []
+    bret = []
+    for name, stack in sorted(threads.items()):
+        ret = ["\n\n# {}".format(name)]
+        pad = None
+        for fn, lno, name, line in traceback.extract_stack(stack):
+            fn = os.sep.join(fn.split(os.sep)[-3:])
+            ret.append('File: "{}", line {}, in {}'.format(fn, lno, name))
+            if line:
+                ret.append("  " + str(line.strip()))
+                if "self.not_empty.wait()" in line:
+                    pad = " " * 4
+
+        if pad:
+            bret += [ret[0]] + [pad + x for x in ret[1:]]
+        else:
+            rret += ret
+
+    return "\n".join(rret + bret)
+
+
 def min_ex():
     et, ev, tb = sys.exc_info()
     tb = traceback.extract_tb(tb, 2)