DUCT(1)

πŸ“¦
version 0.21.0 Β· 3 versions
, in ubuntu stonking

πŸ’– NAME πŸ’–

duct β€” a lightweight wrapper that collects execution data for a command.

Yes bestie, this is the manual for duct, and she's literally just here to hold your hand and keep receipts while your job runs. 🫢


πŸŽ€ DESCRIPTION πŸŽ€

okay so first things first, here's the usage line, verbatim, because it's the blueprint and we do not mess with the blueprint:

usage: duct [-h] [-l {NONE,CRITICAL,ERROR,WARNING,INFO,DEBUG}] [-q]
            [--version] [-p OUTPUT_PREFIX]
            [--summary-format SUMMARY_FORMAT] [--colors] [--clobber]
            [--sample-interval SAMPLE_INTERVAL] [--report-interval REPORT_INTERVAL]
            [--fail-time FAIL_TIME] [-c {all,none,stdout,stderr}]
            [-o {all,none,stdout,stderr}] [-t {all,system-summary,processes-samples}]
            [-m MESSAGE] [--mode {new-session,current-session}]
            command [command_args ...] ...

duct is a lightweight wrapper that collects execution data for an arbitrary command. You can invoke it as either duct or con-duct run β€” two names, one icon. ✨

The execution data she gathers is: execution time, system information, and resource usage statistics of the command and all its child processes. The whole point is to make "wait, how many resources did that command actually need?" not a whole ordeal β€” especially in an HPC environment, where that question is basically a lifestyle. πŸ’…

Resource usage gets determined by polling (at a sample-interval). During execution, duct produces a JSON lines file (see https://jsonlines.org) with one data point recorded for each report (at a report-interval). no cap, it's just receipts in a nice little format.


πŸ¦‹ limitations:

Duct uses session id to track the command process and its children, so it cannot handle the situation where a process creates a new session. And if a command spawns child processes, duct will collect data on them, but heads up: duct exits as soon as the primary process exits. so she's loyal, not immortal. πŸ’€


🌸 environment variables:

Many duct options can be configured by environment variables (which are overridden by command line options β€” CLI stays the main character πŸ‘‘).

  • DUCT_LOG_LEVEL: see --log-level
  • DUCT_OUTPUT_PREFIX: see --output-prefix
  • DUCT_SUMMARY_FORMAT: see --summary-format
  • DUCT_SAMPLE_INTERVAL: see --sample-interval
  • DUCT_REPORT_INTERVAL: see --report-interval
  • DUCT_CAPTURE_OUTPUTS: see --capture-outputs
  • DUCT_MESSAGE: see --message
  • DUCT_CONFIG_PATHS: paths to .env files separated by platform path separator (':' on Unix) (see below)

🧚 .env files:

Environment variables can also be set via .env files. By default, duct searches the following locations (and listen: later files override earlier ones):

  • /etc/duct/.env
  • ${XDG_CONFIG_HOME:-~/.config}/duct/.env
  • .duct/.env

Want to point her somewhere else? Override the search paths by setting DUCT_CONFIG_PATHS with paths separated by the platform path separator (':' on Unix), e.g.:

export DUCT_CONFIG_PATHS="/custom/path.env:~/.myduct.env"

Example .env file content:

# Set default log level
DUCT_LOG_LEVEL=DEBUG

# Configure intervals
DUCT_SAMPLE_INTERVAL=2.0
DUCT_REPORT_INTERVAL=120.0

# Set default output location
DUCT_OUTPUT_PREFIX=~/duct-logs/{datetime}-{pid}_

# Add execution notes (multiline)
DUCT_MESSAGE="Experiment run for paper revision
Using updated dataset from 2025-10-30
See lab notebook page 42 for details"

Supported .env syntax (via python-dotenv):

  • KEY=value (basic assignment)
  • KEY="value with spaces" (quoted values)
  • KEY='single quotes' (single-quoted values)
  • # comments (hash comments)
  • Empty lines (ignored)
  • Multiline values (use quotes)

Precedence (highest to lowest) β€” remember this order, it's giving hierarchy and it matters:

  1. Command line arguments
  2. Explicit environment variables
  3. .env file values (later paths override earlier paths)
  4. Hardcoded defaults

Notes:

  • .env file support requires python-dotenv (pip install con-duct[all])
  • DUCT_CONFIG_PATHS cannot be set in .env files (must be set before loading)
  • Malformed, unreadable, or missing .env files are skipped and logged

πŸ’… positional arguments:

  • command [command_args ...] β€” The command to execute, along with its arguments. this is the star of the show ⭐
  • command_args β€” Arguments for the command.

✨ options:

  • -h, --help β€” show this help message and exit. the classic. πŸŽ€
  • -l, --log-level {NONE,CRITICAL,ERROR,WARNING,INFO,DEBUG} β€” Level of log output to stderr, use NONE to entirely disable. (default: INFO)
  • -q, --quiet β€” [deprecated, use log level NONE] Disable duct logging output (to stderr) (default: False). it's giving legacy, she's tired πŸ’€
  • --version β€” show program's version number and exit.
  • -p, --output-prefix OUTPUT_PREFIX β€” File string format to be used as a prefix for the files -- the captured stdout and stderr and the resource usage logs. The understood variables are {datetime} and {pid}. Leading directories will be created if they do not exist. You can also provide value via DUCT_OUTPUT_PREFIX env variable. (default: .duct/logs/{datetime}-{pid}_)
  • --summary-format SUMMARY_FORMAT β€” Output template to use when printing the summary following execution. Accepts custom conversion flags:
    • !S: Converts filesizes to human readable units, green if measured, red if None.
    • !E: Colors exit code, green if falsey, red if truthy, and red if None.
    • !X: Colors green if truthy, red if falsey.
    • !N: Colors green if not None, red if None

and the default template is:

Summary: Exit Code: {exit_code!E} Command: {command} Log files location: {logs_prefix} Wall Clock Time: {wall_clock_time:.3f} sec Memory Peak Usage (RSS): {peak_rss!S} Memory Average Usage (RSS): {average_rss!S} Virtual Memory Peak Usage (VSZ): {peak_vsz!S} Virtual Memory Average Usage (VSZ): {average_vsz!S} Memory Peak Percentage: {peak_pmem:.2f!N}% Memory Average Percentage: {average_pmem:.2f!N}% CPU Peak Usage: {peak_pcpu:.2f!N}% Average CPU Usage: {average_pcpu:.2f!N}%
  • --colors β€” Use colors in duct output. (default: False). say yes to color, always 🌈
  • --clobber β€” Replace log files if they already exist. (default: False)
  • --sample-interval, --s-i SAMPLE_INTERVAL β€” Interval in seconds between status checks of the running process. Sample interval must be less than or equal to report interval, and it achieves the best results when sample is significantly less than the runtime of the process. Values below 1.0 behave erratically. (default: 1.0)
  • --report-interval, --r-i REPORT_INTERVAL β€” Interval in seconds at which to report aggregated data. (default: 60.0)
  • --fail-time, --f-t FAIL_TIME β€” If command fails in less than this specified time (seconds), duct would remove logs. Set to 0 if you would like to keep logs for a failing command regardless of its run time. Set to negative (e.g. -1) if you would like to not keep logs for any failing command. (default: 3.0)
  • -c, --capture-outputs {all,none,stdout,stderr} β€” Record stdout, stderr, all, or none to log files. You can also provide value via DUCT_CAPTURE_OUTPUTS env variable. (default: all)
  • -o, --outputs {all,none,stdout,stderr} β€” Print stdout, stderr, all, or none to stdout/stderr respectively. (default: all)
  • -t, --record-types {all,system-summary,processes-samples} β€” Record system-summary, processes-samples, or all (default: all)
  • -m, --message MESSAGE β€” Record a descriptive message about the purpose of this execution. You can also provide value via DUCT_MESSAGE env variable. (default: β€” like, empty, she'll leave it blank if you don't say anything πŸ“)
  • --mode {new-session,current-session} β€” Session mode: 'new-session' creates a new session for the command (default), 'current-session' tracks the current session instead of starting a new one. Useful for tracking slurm jobs or other commands that should run in the current session. (default: new-session)

and that's the whole page, bestie!! 🌟 this is duct 0.21.0, dated July 2026, and she ate. go run something and let her write it all down. πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from