static S renderUserThreadsWithStackTraces() { ret renderUserThreadsWithStackTraces(mapWithout(Thread.getAllStackTraces(), currentThread())); } static S renderUserThreadsWithStackTraces(Map threadMap) { ret renderUserThreadsWithStackTraces(threadMap, "thread"); } static S renderUserThreadsWithStackTraces(Map threadMap, S threadDescription) { new StringBuilder bufRunnable; new StringBuilder bufWaiting; int nRunnable = 0, nWaiting = 0, nSystem = 0; for (t, stackTrace : threadMap) { bool runnable = isThreadRunnable_x(t, stackTrace); var buf = runnable ? bufRunnable : bufWaiting; ThreadGroup g = t.getThreadGroup(); if (g != null && g.getName().equals("system")) continue with ++nSystem; if (runnable) ++nRunnable; else ++nWaiting; printTo(buf, t); for (StackTraceElement e : threadMap.get(t)) printTo(buf, " " + e); printTo(buf); } ret asciiHeading2(n2(nRunnable, "runnable " + threadDescription)) + "\n" + bufRunnable + asciiHeading2(n2(nWaiting, "sleeping " + threadDescription) + " (+" + n2(nSystem, "system thread") + ")") + "\n" + bufWaiting; }