/usr/lib/python3/dist-packages/visidata/async.py is in visidata 1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import ctypes
import threading
import pstats
import cProfile
from .vdtui import *
min_thread_time_s = 0.10 # only keep threads that take longer than this number of seconds
option('profile_threads', False, 'profile async threads')
option('min_memory_mb', 0, 'minimum memory to continue loading and async processing')
globalCommand('^C', 'cancelThread(*sheet.currentThreads or error("no active threads on this sheet"))', 'abort all threads on current sheet')
globalCommand('g^C', 'cancelThread(*vd.threads or error("no threads"))', 'abort all secondary threads')
globalCommand('^T', 'vd.push(vd.threadsSheet)', 'open Threads Sheet')
globalCommand('^_', 'toggleProfiling(threading.current_thread())', 'turn profiling on for main process')
class ProfileSheet(TextSheet):
commands = TextSheet.commands + [
Command('z^S', 'profile.dump_stats(input("save profile to: ", value=name+".prof"))', 'save profile'),
]
def __init__(self, name, pr):
super().__init__(name, getProfileResults(pr).splitlines())
self.profile = pr
def toggleProfiling(t):
if not t.profile:
t.profile = cProfile.Profile()
t.profile.enable()
status('profiling of main thread enabled')
else:
t.profile.disable()
status('profiling of main thread disabled')
# define @async for potentially long-running functions
# when function is called, instead launches a thread
# ENTER on that row pushes a profile of the thread
class ThreadProfiler:
def __init__(self, thread):
self.thread = thread
if options.profile_threads:
self.thread.profile = cProfile.Profile()
else:
self.thread.profile = None
def __enter__(self):
if self.thread.profile:
self.thread.profile.enable()
return self
def __exit__(self, exc_type, exc_val, tb):
if self.thread.profile:
self.thread.profile.disable()
# remove very-short-lived async actions
if elapsed_s(self.thread) < min_thread_time_s:
vd().threads.remove(self.thread)
@functools.wraps(vd().toplevelTryFunc)
def threadProfileCode(vdself, func, *args, **kwargs):
'Profile @async threads if `options.profile_threads` is set.'
with ThreadProfiler(threading.current_thread()) as prof:
try:
prof.thread.status = threadProfileCode.__wrapped__(vdself, func, *args, **kwargs)
except EscapeException as e:
prof.thread.status = e
def getProfileResults(pr):
s = io.StringIO()
ps = pstats.Stats(pr, stream=s)
ps.strip_dirs()
ps.sort_stats('cumulative')
ps.print_stats()
return s.getvalue()
def cancelThread(*threads, exception=EscapeException):
'Raise exception on another thread.'
for t in threads:
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(t.ident), ctypes.py_object(exception))
SheetsSheet.commands += [
Command('^C', 'cancelThread(*cursorRow.currentThreads)', 'abort all threads on sheet at cursor'),
]
SheetsSheet.columns += [
ColumnAttr('threads', 'currentThreads', type=len),
]
# each row is an augmented threading.Thread object
class ThreadsSheet(Sheet):
rowtype = 'threads'
commands = [
Command('d', 'cancelThread(cursorRow)', 'abort thread at current row'),
Command('^C', 'd'),
Command(ENTER, 'vd.push(ProfileSheet(cursorRow.name+"_profile", cursorRow.profile))', 'push profile sheet for this action'),
]
columns = [
ColumnAttr('name'),
Column('process_time', type=float, getter=lambda col,row: elapsed_s(row)),
ColumnAttr('profile'),
ColumnAttr('status'),
]
def reload(self):
self.rows = vd().threads
def elapsed_s(t):
return (t.endTime or time.process_time())-t.startTime
def checkMemoryUsage(vs):
min_mem = options.min_memory_mb
if min_mem and vd().unfinishedThreads:
tot_m, used_m, free_m = map(int, os.popen('free --total --mega').readlines()[-1].split()[1:])
ret = '[%dMB]' % free_m
if free_m < min_mem:
attr = 'red'
status('%dMB free < %dMB minimum, stopping threads' % (free_m, min_mem))
cancelThread(*vd().unfinishedThreads)
curses.flash()
else:
attr = 'green'
return ret, attr
vd().threadsSheet = ThreadsSheet('thread_history')
vd().toplevelTryFunc = threadProfileCode
vd().addHook('rstatus', checkMemoryUsage)
|