Skip to main content

L_bash_profile: Bash Script Profiler

L_bash_profile is a powerful and easy-to-use command-line tool for profiling Bash scripts. It helps you identify performance bottlenecks and understand the execution flow of your scripts, making it an essential tool for both beginner and experienced Bash developers.

Key Features

  • Deterministic Profiling: Get a complete and accurate trace of your script's execution.
  • Performance Analysis: Identify hotspots in your code by analyzing the collected profile data.
  • Multiple Report Formats:
    • Top Longest Commands: See a list of the most time-consuming commands.
    • Top Longest Functions: Identify the slowest functions.
    • Call Graphs: Visualize the execution flow with dot call graphs.
    • Python-like Profiling: Generate pstats files compatible with Python's profiling ecosystem (e.g., snakeviz).
    • Structured JSON Export: Output detailed analysis or comparison results in machine-readable JSON format to stdout with -j / --json.
  • Easy to Use: Simple and intuitive command-line interface.

Installation

You can install L_bash_profile using uv:

uv tool install L_bash_profile

Or run it directly without installing:

uvx L_bash_profile --help

Basic Example

$ L_bash_profile run 'i=0; while ((i < 100)); do ((i++)); done'
PROFILING: i=0; while ((i < 100)); do ((i++)); done to /dev/stdout
PROFING ENDED, output in /dev/stdout
Top 3 cummulatively longest commands:
  percent    spent_us  cmd                   calls    spentPerCall  topCaller1    topCaller2    topCaller3    example
---------  ----------  ------------------  -------  --------------  ------------  ------------  ------------  ---------
 49.5498        1_596  \(\(i\ \<\ 100\)\)      101          15.802  \> 101                                    \<:7
 48.9289        1_576  \(\(i++\)\)             100          15.76   \> 100                                    \<:7
  1.52127          49  i=0                       1          49      \> 1                                      \<:7
...

Subcommands

run

Profiles and analyzes a Bash script in one go. This is a convenient shortcut that combines the profile and analyze steps.

$ L_bash_profile run --qemu --callstatscmds 'f() { :; }; f'
PROFILING: 'f() { :; }; f' to /tmp/L_bash_profile__630o4w7.txt
PROFILING ENDED, output in /tmp/L_bash_profile__630o4w7.txt
Top 10 longest commands (total):
      %  us       cmd       calls  spent/call    caller1    caller2    caller3    location
-------  -------  ------  -------  ------------  ---------  ---------  ---------  ----------
64.1166  192,869  f             2  96,434.5      \>         f                     \<:6
35.8834  107,941  :             1  107,941       f                                bash:6
 0       0        :\ END        1  0             \>                               \<:7

Top 10 longest commands (per call):
      %  us       cmd       calls  spent/call    caller1    caller2    caller3    location
-------  -------  ------  -------  ------------  ---------  ---------  ---------  ----------
35.8834  107,941  :             1  107,941       f                                bash:6
64.1166  192,869  f             2  96,434.5      \>         f                     \<:6
 0       0        :\ END        1  0             \>                               \<:7

Top 10 longest functions (total):
      %  incl     excl     func         calls  spent/call    location
-------  -------  -------  ---------  -------  ------------  ----------
65.3934  196,710  196,710  bash:6(f)        1  196,710       bash:6

Top 10 longest functions (per call):
      %  incl     excl     func         calls  spent/call    location
-------  -------  -------  ---------  -------  ------------  ----------
65.3934  196,710  196,710  bash:6(f)        1  196,710       bash:6

Call Tree (top 3 children):
└── bash:6(f) 196,710ins 65.4%

Script executed in 300810 instructions, 4 instructions, 2 functions.

compare

Compares the performance of multiple Bash code snippets. It runs the snippets and presents a comparison table. Supports --qemu for deterministic, warm-calibrated instruction counts, --prefix to set up initial state, and --suffix / -S to execute a common command after each snippet.

$ L_bash_profile compare --qemu --prefix 'trim() { a=${a//1}; };' '' 'a=11' 'a=22'
Benchmarking 1/3: ''
Benchmarking 2/3: a=11
Benchmarking 3/3: a=22
Comparison results (method: QEMU, repeat: 1):
| Code   | ExitCode   | Insn   | ΔInsn   |
|--------|------------|--------|---------|
| ''     | 0          | 6580   | -       |
| a=11   | 0          | 21623  | +15043  |
| a=22   | 0          | 21623  | +0      |

profile

Executes a Bash script and generates a profile file containing the execution trace. It supports different methods:

  • DEBUG trap: Most reliable for general use.
  • XTRACE (set -x): Fastest, but shows expanded commands.
  • VAR: Uses an array to collect data, avoids some subshell issues.
  • QEMU: High-precision, instruction-level profiling using QEMU User-Mode Emulation. See QEMU Profiling Documentation for details.

analyze

Analyzes a profile file and generates human-readable reports. It can also output visualization files:

  • --callgraph: Full execution trace in DOT format.
  • --callstats: Statistics-based callgraph in DOT format.
  • --pstats: Python pstats file for use with tools like snakeviz.
  • --qemu: Use instruction-counting logic for QEMU trace files.

Real-World Example: Profiling L_lib.sh

This example demonstrates profiling complex argument parsing logic within the L_lib.sh library.

$ L_bash_profile profile -o profile.txt 'export L_UNITTEST_UNSET_X=0; . ../L_lib/tests/argparse_uv.sh -n NO --cache-dir CACHE_DIR add --color --no-build-package' -m XTRACE
PROFILING: 'export L_UNITTEST_UNSET_X=0; . ../L_lib/tests/argparse_uv.sh -n NO --cache-dir CACHE_DIR add --color --no-build-package' to profile.txt
PROFING ENDED, output in profile.txt

$ L_bash_profile analyze profile.txt --filterfunction L_argparse --dotlimit 6
Top 20 cummulatively longest commands:
  percent    spent_us  cmd                                                   calls    spentPerCall  topCaller1                            topCaller2                                    topCaller3                              example
---------  ----------  --------------------------------------------------  -------  --------------  ------------------------------------  --------------------------------------------  --------------------------------------  -------------------------------------------------------
  9.19311      38_574  'case "${_L_args[_L_argsi]}" in'                       3195        12.0732   _L_argparse_spec_call_parameter 3132  _L_argparse_spec_parse_args 57                _L_argparse_parse_args_parse_options 5  ../bin/L_lib.sh:8325
  5.1988       21_814  '(( ++_L_argsi < 3140 ))'                              3057         7.13575  _L_argparse_spec_call_parameter 3056  _L_argparse_spec_call_subparser 1                                                     ../bin/L_lib.sh:7141
  4.13206      17_338  '(( 1 ))'                                              2418         7.17039  _L_argparse_spec_call_parameter 2399  _L_argparse_spec_parse_args 19                                                        ../bin/L_lib.sh:8324
  3.94545      16_555  '_L_argparse_spec_call_parameter'                       752        22.0146   _L_argparse_spec_parse_args 733       _L_argparse_spec_call 19                                                              ../bin/L_lib.sh:8295
  3.53363      14_827  '_L_argparse_spec_argument_common'                      753        19.6906   _L_argparse_spec_call_parameter 752   _L_argparse_spec_common_subparser_function 1                                          ../bin/L_lib.sh:7254
  3.45832      14_511  "[[ -n '' ]]"                                          1599         9.07505  _L_argparse_spec_call_parameter 1131  L_var_is_set 427                              _L_argparse_spec_parse_args 38          ../bin/L_lib.sh:1882
  2.65541      11_142  '_L_argparse_spec_call_parameter_common_option_a..      889        12.5332   _L_argparse_spec_call_parameter 889                                                                                         ../bin/L_lib.sh:7171
  2.14873       9_016  'local first_long_option= first_short_option= pc..      752        11.9894   _L_argparse_spec_call_parameter 752                                                                                         ../bin/L_lib.sh:7139
  1.98714       8_338  'case "${_L_args[++_L_argsi]}" in'                      734        11.3597   _L_argparse_spec_parse_args 734                                                                                             ../bin/L_lib.sh:8406
  1.97118       8_271  'case "${_L_opt_action[_L_opti]:=store}" in'            753        10.9841   _L_argparse_spec_argument_common 753                                                                                        ../bin/L_lib.sh:7276
  1.87823       7_881  "[[ '' == remainder ]]"                                 750        10.508    _L_argparse_spec_argument_common 750                                                                                        ../bin/L_lib.sh:7260
  1.8451        7_742  "L_var_is_set '_L_opt_dest[_L_opti]'"                   745        10.3919   _L_argparse_spec_call_parameter 745                                                                                         ../bin/L_lib.sh:7189
  1.77956       7_467  '((  3140 - _L_argsi >= 2 ))'                           753         9.91633  _L_argparse_spec_parse_args 753                                                                                             ../bin/L_lib.sh:8404
  1.67423       7_025  'break'                                                 755         9.30464  _L_argparse_spec_call_parameter 733   _L_argparse_spec_parse_args 19                _L_argparse_spec_call_subparser 1       ../bin/L_lib.sh:8327
  1.58605       6_655  'case "${_L_opt_nargs[_L_opti]:=0}" in'                 753         8.83798  _L_argparse_spec_argument_common 753                                                                                        ../bin/L_lib.sh:7327
  1.5634        6_560  'local _L_type='                                        752         8.7234   _L_argparse_spec_call_parameter 752                                                                                         ../bin/L_lib.sh:7206
  1.52408       6_395  '_L_opt__class[_L_opti]=option'                         745         8.58389  _L_argparse_spec_call_parameter 745                                                                                         ../bin/L_lib.sh:7187
  1.50096       6_298  '(( --_L_argsi ))'                                      753         8.36388  _L_argparse_spec_call_parameter 752   _L_argparse_spec_call_subparser 1                                                     ../bin/L_lib.sh:7141
  1.37823       5_783  '(( 0 ))'                                               755         7.6596   _L_argparse_spec_call_parameter 751   _L_argparse_optspec_validate_values 2         _L_argparse_parse_args_long_option 2    ../bin/L_lib.sh:7243
  1.34105       5_627  '(( ++_L_opti ))'                                       753         7.47278  _L_argparse_spec_parse_args 753                                                                                             ../bin/L_lib.sh:8397

Top 20 cummulatively longest functions:
  percent    spent_us  funcname                                                calls    spentPerCall    instructions    instructionsPerCall  location
---------  ----------  ----------------------------------------------------  -------  --------------  --------------  ---------------------  -------------------------------------------------------
51.682        216_856  _L_argparse_spec_call_parameter                           752       288.372             22857               30.3949   ../bin/L_lib.sh:7139
14.4767        60_744  _L_argparse_spec_argument_common                          753        80.6693             4885                6.48738  ../bin/L_lib.sh:7260
11.4705        48_130  _L_argparse_spec_parse_args                                19      2533.16               4238              223.053    ../bin/L_lib.sh:8323
 9.46885       39_731  _L_argparse_spec_call_parameter_common_option_assign      889        44.6918             2667                3        ../bin/L_lib.sh:7124
 5.33345       22_379  L_is_valid_variable_name                                  752        29.7593              752                1        ../bin/L_lib.sh:2532
 2.02861        8_512  L_argparse                                                  1      8512                    49               49        ../bin/L_lib.sh:8506
 1.86417        7_822  L_var_is_set                                              833         9.39016             833                1        ../bin/L_lib.sh:1882
 0.666592       2_797  _L_argparse_parse_args_set_defaults                         2      1398.5                 334              167        ../bin/L_lib.sh:7974
 0.465685       1_954  _L_argparse_parse_args                                      2       977                   234              117        ../bin/L_lib.sh:8153
 0.316494       1_328  _L_argparse_spec_call_subparser                             1      1328                   136              136        ../bin/L_lib.sh:6874
 0.284082       1_192  _L_argparse_parser_get_long_option                          3       397.333                 6                2        ../bin/L_lib.sh:7373
 0.244282       1_025  L_is_true                                                 103         9.95146             103                1        ../bin/L_lib.sh:2484
 0.232366         975  _L_argparse_parse_args_long_option                          3       325                    39               13        ../bin/L_lib.sh:8005
 0.230698         968  _L_argparse_parse_args_short_option                         1       968                    19               19        ../bin/L_lib.sh:8074
 0.219735         922  _L_argparse_spec_call                                      19        48.5263               57                3        ../bin/L_lib.sh:8294
 0.217828         914  _L_argparse_optspec_dest_store                             46        19.8696               92                2        ../bin/L_lib.sh:7435
 0.160154         672  _L_argparse_spec_subparser_inherit_from_parent             18        37.3333               54                3        ../bin/L_lib.sh:8307
 0.157055         659  _L_argparse_sub_subparser_choices_indexes                   1       659                    76               76        ../bin/L_lib.sh:6956
 0.153481         644  L_array_append                                             36        17.8889               72                2        ../bin/L_lib.sh:3412
 0.13513          567  _L_argparse_optspec_dest_arr_clear                          1       567                     3                3        ../bin/L_lib.sh:7423

Script executed in 0:00:00.419597us, 37891 instructions, 28 functions.

Visualizing Call Graphs

To visualize the execution flow with xdot:

L_bash_profile analyze profile.txt --callstats profile.dot
xdot profile.dot

exampledot

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.

License

This project is licensed under the GPLv3 License. See the LICENSE.txt file for details.

Self promotion

Check out my other shell related projects: mkdocstrings-sh, L_lib, L_builtin.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

l_bash_profile-0.1.5.tar.gz (70.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

l_bash_profile-0.1.5-py3-none-any.whl (52.2 kB view details)

Uploaded Python 3

File details

Details for the file l_bash_profile-0.1.5.tar.gz.

File metadata

  • Download URL: l_bash_profile-0.1.5.tar.gz
  • Upload date:
  • Size: 70.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for l_bash_profile-0.1.5.tar.gz
Algorithm Hash digest
SHA256 9ab181b1026697e734da07d9a0a9963d4f9ce0b7e5f3350152967f4de55fd890
MD5 7606e891f0c27b20bd32811b91e95d72
BLAKE2b-256 4e4e9bfdb90e80bb4036267621393261674bfeb01818505b6416f5adfbb038d4

See more details on using hashes here.

Provenance

The following attestation bundles were made for l_bash_profile-0.1.5.tar.gz:

Publisher: workflow.yml on Kamilcuk/L_bash_profile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file l_bash_profile-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: l_bash_profile-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 52.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for l_bash_profile-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 e366370f0d3d91f00a7cc61107f1c1c9389a6fa3fe2dadc7744f7dd18f153371
MD5 b56e7f22121b8d571f61ade46f98271b
BLAKE2b-256 e4c2ec2159ef9eea25fbdd5d5d22d4e6e89d2b3cab596c6b4cd081ddcd89d0f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for l_bash_profile-0.1.5-py3-none-any.whl:

Publisher: workflow.yml on Kamilcuk/L_bash_profile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.5 This release

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page