diff --git a/line_profiler/line_profiler.py b/line_profiler/line_profiler.py index 13514f64..27fbe48d 100755 --- a/line_profiler/line_profiler.py +++ b/line_profiler/line_profiler.py @@ -298,13 +298,10 @@ def show_func(filename, start_lineno, func_name, timings, unit, } ALLOW_SCIENTIFIC_NOTATION = 1 - col_order = ['line', 'hits', 'time', 'perhit', 'percent'] - template = '%6s %9s %12s %8s %8s %-s' - display = {} # Loop over each line to determine better column formatting. - # Fallback to scientific notation if columns are larger. + # Fallback to scientific notation if columns are larger than a threshold. for lineno, nhits, time in timings: if total_time == 0: # Happens rarely on empty function percent = '' @@ -312,28 +309,31 @@ def show_func(filename, start_lineno, func_name, timings, unit, percent = '%5.1f' % (100 * time / total_time) time_disp = '%5.1f' % (time * scalar) - if len(time_disp) > default_column_sizes['time'] and ALLOW_SCIENTIFIC_NOTATION: + if ALLOW_SCIENTIFIC_NOTATION and len(time_disp) > default_column_sizes['time']: time_disp = '%5.1g' % (time * scalar) perhit_disp = '%5.1f' % (float(time) * scalar / nhits) - if len(perhit_disp) > default_column_sizes['perhit'] and ALLOW_SCIENTIFIC_NOTATION: + if ALLOW_SCIENTIFIC_NOTATION and len(perhit_disp) > default_column_sizes['perhit']: perhit_disp = '%5.1g' % (float(time) * scalar / nhits) nhits_disp = "%d" % nhits - if len(nhits_disp) > default_column_sizes['hits'] and ALLOW_SCIENTIFIC_NOTATION: + if ALLOW_SCIENTIFIC_NOTATION and len(nhits_disp) > default_column_sizes['hits']: nhits_disp = '%g' % nhits display[lineno] = (nhits_disp, time_disp, perhit_disp, percent) - max_hitlen = max(len(t[0]) for t in display.values()) - max_timelen = max(len(t[1]) for t in display.values()) - max_perhitlen = max(len(t[2]) for t in display.values()) - # Expand column sizes if the numbers are large. column_sizes = default_column_sizes.copy() - column_sizes['hits'] = max(column_sizes['hits'], max_hitlen) - column_sizes['time'] = max(column_sizes['time'], max_timelen) - column_sizes['perhit'] = max(column_sizes['perhit'], max_perhitlen) + if len(display): + max_hitlen = max(len(t[0]) for t in display.values()) + max_timelen = max(len(t[1]) for t in display.values()) + max_perhitlen = max(len(t[2]) for t in display.values()) + column_sizes['hits'] = max(column_sizes['hits'], max_hitlen) + column_sizes['time'] = max(column_sizes['time'], max_timelen) + column_sizes['perhit'] = max(column_sizes['perhit'], max_perhitlen) + + # template = '%6s %9s %12s %8s %8s %-s' + col_order = ['line', 'hits', 'time', 'perhit', 'percent'] template = ' '.join(['%' + str(column_sizes[k]) + 's' for k in col_order]) template = template + ' %-s' diff --git a/tests/complex_example.py b/tests/complex_example.py new file mode 100644 index 00000000..ca264193 --- /dev/null +++ b/tests/complex_example.py @@ -0,0 +1,121 @@ +""" +A script used in test_complex_case.py +""" +import line_profiler +import atexit + + +profile = line_profiler.LineProfiler() + + +@atexit.register +def _show_profile_on_end(): + profile.print_stats() + + +@profile +def fib(n): + a, b = 0, 1 + while a < n: + print(a, end=' ') + a, b = b, a + b + print() + + +@profile +def funcy_fib(n): + """ + Alternatite fib function where code splits out over multiple lines + """ + a, b = ( + 0, 1 + ) + while a < n: + print( + a, end=' ') + a, b = b, \ + a + b + print( + ) + + +@profile +def fib_only_called_by_thread(n): + a, b = 0, 1 + while a < n: + print(a, end=' ') + a, b = b, a + b + print() + + +@profile +def fib_only_called_by_process(n): + a, b = 0, 1 + while a < n: + print(a, end=' ') + a, b = b, a + b + # FIXME: having two functions with the EXACT same code can cause issues + # a = 'no longer exactly the same' + print() + + +@profile +def main(): + """ + Run a lot of different Fibonacci jobs + """ + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--size', type=int, default=10) + args = parser.parse_args() + + size = args.size + + for i in range(size): + fib(i) + funcy_fib( + i) + fib(i) + + from concurrent.futures import ThreadPoolExecutor + executor = ThreadPoolExecutor(max_workers=4) + with executor: + jobs = [] + for i in range(size): + job = executor.submit(fib, i) + jobs.append(job) + + job = executor.submit(funcy_fib, i) + jobs.append(job) + + job = executor.submit(fib_only_called_by_thread, i) + jobs.append(job) + + for job in jobs: + job.result() + + from concurrent.futures import ProcessPoolExecutor + executor = ProcessPoolExecutor(max_workers=4) + with executor: + jobs = [] + for i in range(size): + job = executor.submit(fib, i) + jobs.append(job) + + job = executor.submit(funcy_fib, i) + jobs.append(job) + + job = executor.submit(fib_only_called_by_process, i) + jobs.append(job) + + for job in jobs: + job.result() + + +if __name__ == '__main__': + """ + CommandLine: + cd ~/code/line_profiler/tests/ + python complex_example.py --size 10 + """ + main() diff --git a/tests/test_complex_case.py b/tests/test_complex_case.py new file mode 100644 index 00000000..0ba80c16 --- /dev/null +++ b/tests/test_complex_case.py @@ -0,0 +1,58 @@ + + +def profile_now(func): + """ + Wrap a function to print profile information after it is called. + + Args: + func (Callable): function to profile + + Returns: + Callable: the wrapped function + """ + import line_profiler + profile = line_profiler.LineProfiler() + new_func = profile(func) + + def wraper(*args, **kwargs): + try: + return new_func(*args, **kwargs) + except Exception: + pass + finally: + profile.print_stats(stripzeros=True) + + wraper.new_func = new_func + return wraper + + +def func_to_profile(): + list(range(100000)) + tuple(range(100000)) + set(range(100000)) + + +def test_profile_now(): + func = func_to_profile + profile_now(func)() + + +def test_complex_example(): + import sys + import pathlib + import subprocess + + try: + test_dpath = pathlib.Path(__file__).parent + except NameError: + # for development + test_dpath = pathlib.Path('~/code/line_profiler/tests').expanduser() + + complex_fpath = test_dpath / 'complex_example.py' + + proc = subprocess.run([sys.executable, complex_fpath], capture_output=True, + universal_newlines=True) + print(proc.stdout) + print(proc.stderr) + print(proc.returncode) + proc.check_returncode()