Skip to content

Generating DAGs

sat edited this page Jul 15, 2026 · 1 revision

Generating DAGs

This page explains how a user generates causal DAGs end to end with the logdag CLI: the make-args -> make-dag command sequence, the argument-file model that splits the analysis term into per-job windows, time binning, parallelism, and the on-disk layout of the resulting DAG files.

It focuses on phase 2 of the pipeline (DAG generation). Phase 1 (building the feature DB) is covered in Data Sources; the algorithms invoked inside each job are covered in Causal Discovery Methods; all settings referenced here are listed in Configuration and Configuration Options; the full subcommand reference is in CLI Commands.

Grounding: this page is written against logdag/makedag.py (makedag_main, make_input, estimate_dag), logdag/arguments.py (ArgumentManager, all_args), logdag/log2event.py (makeinput, load_event), and the make-args / make-dag / make-dag-stdin / dump-input handlers in logdag/__main__.py. CLI -h output and the argument-file example below were produced by running the commands in the logenv venv.

The two core commands

DAG generation is driven by two subcommands of the logdag tool:

Command What it does
logdag make-args Enumerate the analysis jobs from the config and write the argument file (<output_dir>/args). No DAG is computed.
logdag make-dag Enumerate the same jobs and run causal inference for each, writing the DAG files.

make-dag regenerates the argument file itself, so a separate make-args call is not required before make-dag. make-args is useful when you want to inspect or partition the job list first (for example, to drive a job scheduler that calls make-dag-stdin per job; see Parallelism).

A minimal end-to-end run, assuming a feature DB has already been built (logdag.source make-evdb-log-all, see Data Sources):

# (optional) inspect the planned jobs first
logdag make-args -c logdag.conf
logdag show-args -c logdag.conf

# generate all DAGs
logdag make-dag -c logdag.conf

With the logenv venv the CLI is invoked as python -m logdag:

/Users/sat/project/logenv/.venv/bin/python -m logdag make-dag -c logdag.conf

The real -h output for make-dag:

$ python -m logdag make-dag -h
usage: __main__.py make-dag [-h] [-c CONFIG] [--debug] [-p PARALLEL]

Generate causal DAGs

options:
  -h, --help            show this help message and exit
  -c CONFIG, --config CONFIG
                        configuration file path for amulog
  --debug               set logging level to debug (default: info)
  -p PARALLEL, --parallel PARALLEL
                        number of processes in parallel

Note: the -c/--config help text reads "configuration file path for amulog", but the file you pass is the logdag config; the amulog config is referenced from inside it via [database_amulog] source_conf (see Configuration). The help string is shared across the CLI.

The argument-file model

logdag does not estimate one global DAG. Instead it splits the whole analysis period into a set of overlapping (or non-overlapping) time windows, optionally crossed with several areas (host groupings), and estimates one DAG per (time-window, area) pair. Each such pair is a job, identified by a jobname.

How jobs are enumerated

arguments.all_args(conf) builds the job list from three [dag] settings:

  • whole_term — the entire period to analyse (start, end).
  • unit_term — the length of each job's time window.
  • unit_diff — the step between consecutive window start times.

The window start slides from whole_term[0] by unit_diff each step; for each start it emits one window [top, top + unit_term) per configured area:

# arguments.all_args (paraphrased)
top_dt = whole_start
while top_dt < whole_end:
    end_dt = top_dt + unit_term
    for area in areas:
        l_args.append((conf, (top_dt, end_dt), area))
    top_dt = top_dt + unit_diff

Each job is a 3-tuple (conf, dt_range, area) — this tuple is the unit of work passed to makedag.makedag_main. Because unit_term and unit_diff are independent, windows can overlap (unit_term > unit_diff), tile exactly (unit_term == unit_diff), or leave gaps (unit_term < unit_diff). The defaults in config.conf.default are unit_term = 30h, unit_diff = 24h, i.e. one 30-hour window per day, overlapping by 6 hours.

Areas

[dag] area controls the host grouping each DAG covers:

  • area = all — treat all hosts as a single area named all (one DAG per window).
  • area = each — treat each host as its own area (one DAG per host per window).
  • area = core, area1, area2 — explicit named areas, defined via area_def.

The list of areas comes from config.getlist(conf, "dag", "area"), so multiple comma-separated areas multiply the number of jobs.

ArgumentManager

arguments.ArgumentManager holds the job list and manages its on-disk form:

  • am.generate(arguments.all_args) — populate am.l_args by calling all_args.
  • am.init_dirs(conf) — directory-init hook (currently a no-op kept for compatibility; the per-job directories are created lazily when a DAG/evdef path is first requested).
  • am.dump() — write the argument file at <output_dir>/args, one jobname per line. <output_dir> is [dag] output_dir.
  • am.load() — read the argument file back, reconstructing each (conf, dt_range, area) tuple via jobname2args.
  • am.show() — render the job table used by show-args.

Both make-args and make-dag follow the same prelude: open_logdag_config -> ArgumentManager(conf) -> generate(all_args) -> init_dirs -> dump. make-dag then additionally runs inference over am.

Jobnames and the round-trip

A jobname encodes the area and the window start datetime: "<area>_<datestr>", where <datestr> is YYYYmmdd when the window starts on a date boundary and YYYYmmdd_HHMMSS otherwise (ArgumentManager.jobname). The reverse parser jobname2args recovers the area and start datetime and rebuilds the window end as start + unit_term. The parser matches the datetime suffix with a regex, so areas that themselves contain _ (e.g. host_web01) round-trip correctly.

Consequence: the argument file stores only the jobname, and the window length is recovered from the current config's unit_term at load time. If you change unit_term between make-args and a later command that loads the args, the reconstructed windows will use the new length. (Confirmed from jobname2args, which reads unit_term from conf rather than from the file.)

Worked example (real output)

A throwaway config with an explicit term and two areas:

[general]

[dag]
whole_term = 2112-09-01 00:00:00, 2112-09-04 00:00:00
area = all, host1
unit_term = 30h
unit_diff = 24h
output_dir = .../pc_output

Running make-args then show-args (real output from the venv):

$ python -m logdag make-args -c demo.conf
$ cat pc_output/args
all_21120901_000000
host1_21120901_000000
all_21120902_000000
host1_21120902_000000
all_21120903_000000
host1_21120903_000000

$ python -m logdag show-args -c demo.conf
name                  | datetime                                              | area
all_21120901_000000   | 2112-09-01 00:00:00+09:00 - 2112-09-02 06:00:00+09:00 | all
host1_21120901_000000 | 2112-09-01 00:00:00+09:00 - 2112-09-02 06:00:00+09:00 | host1
all_21120902_000000   | 2112-09-02 00:00:00+09:00 - 2112-09-03 06:00:00+09:00 | all
host1_21120902_000000 | 2112-09-02 00:00:00+09:00 - 2112-09-03 06:00:00+09:00 | host1
all_21120903_000000   | 2112-09-03 00:00:00+09:00 - 2112-09-04 06:00:00+09:00 | all
host1_21120903_000000 | 2112-09-03 00:00:00+09:00 - 2112-09-04 06:00:00+09:00 | host1

Observe directly from this output:

  • The 3-day whole_term stepped by unit_diff = 24h produces 3 window starts.
  • Two areas (all, host1) double that to 6 jobs.
  • Each window spans unit_term = 30h: e.g. 2112-09-01 00:00 - 2112-09-02 06:00, so consecutive windows overlap by 6 hours.
  • The jobname uses the YYYYmmdd_HHMMSS form here because the window start carries an explicit time-of-day component.

make-args/show-args only read the config — no feature DB or amulog DB is required to plan and inspect the job list, which is why the example above runs standalone. Computing the DAGs (make-dag) does require a populated feature DB.

What one job does (make-dag internals)

For each job tuple, make-dag calls makedag.makedag_pool -> makedag.makedag_main(args, do_dump=True). makedag_main orchestrates:

  1. Skip-if-exists. If [dag] pass_dag_exists = true and the DAG file already exists, the job is skipped (returns None). This makes re-runs resumable.
  2. Build the input via log2event.makeinput(conf, dt_range, area): load all events for the window from the feature DB (or directly from amulog when use_evdb = false), bin them into a time-series matrix, and produce a pandas DataFrame (rows = time bins, columns = event IDs) plus an EventDefinitionMap (the eid <-> event-definition mapping). If there is no data in the window, makeinput returns (None, None) and the job is skipped.
  3. Dump the event map (evmap.dump(args)) so later display/filter commands can resolve node IDs.
  4. Prior knowledge (pknowledge.init_prior_knowledge) — optional constraints for the search (see Causal Discovery Methods).
  5. Estimate the DAG via makedag.estimate_dag, which dispatches on [dag] cause_algorithm to the appropriate method adapter.
  6. Persist the result as a showdag.LogDAG (NetworkX DiGraph + event map).

makedag_main returns None for an empty/skipped job. The production path (make-dag) passes do_dump=True and ignores the return value, so a None result is harmless there. (Documented in the makedag_main docstring; callers that aggregate over many jobs must skip None.)

Binning and time range

The time range of each job is the job's dt_range (the unit_term window). Within that window, events are discretized into bins according to three [dag] settings, read inside log2event (load_event):

  • ci_bin_method — one of sequential, slide, radius.
  • ci_bin_size — the width of each bin (e.g. 1m).
  • ci_bin_diff — the spacing between bin centers/starts.

The three methods differ in how bins are placed:

ci_bin_method Behaviour
sequential Non-overlapping consecutive bins of width ci_bin_size. ci_bin_diff is ignored (treated as equal to ci_bin_size).
slide Sliding bins of width ci_bin_size stepped by ci_bin_diff (ci_bin_diff < ci_bin_size gives overlapping bins).
radius Bins centered on a grid spaced by ci_bin_diff, each covering a radius of 0.5 * ci_bin_size.

The binned values become the rows of the input DataFrame; columns are event IDs. Whether the values are counts or binarized depends on the independence test / algorithm chosen (see Causal Discovery Methods). The defaults are ci_bin_method = sequential, ci_bin_size = ci_bin_diff = 1m.

Inspecting the input matrix

You can materialize the exact input DataFrame for a single job without running inference, which is useful for debugging feature extraction and binning:

$ python -m logdag dump-input -h
usage: __main__.py dump-input [-h] [-c CONFIG] [--debug] [-o OUTPUT] [-b] TASKNAME

Output causal analysis input in pandas csv format

positional arguments:
  TASKNAME              argument name

options:
  -h, --help            show this help message and exit
  -c CONFIG, --config CONFIG
                        configuration file path for amulog
  --debug               set logging level to debug (default: info)
  -o OUTPUT, --output OUTPUT
                        output filename
  -b, --binary          dump binarized dataframe csv

dump-input <jobname> writes the (rows = bins x cols = event-ids) matrix to CSV (-o), using the same makeinput path that make-dag uses. -b dumps the binarized form. (Requires a populated feature DB, so no sample output is shown here.)

Parallelism and distributed runs

make-dag accepts the -p/--parallel N CLI flag (default N=1, i.e. sequential):

  • N == 1 — jobs run sequentially in the current process.
  • N > 1 — jobs are distributed across a multiprocessing.Pool of N processes (pool.map(makedag.makedag_pool, am)), one job tuple per task.
# run all jobs across 8 worker processes
python -m logdag make-dag -c logdag.conf -p 8

Because each job is independent and writes to its own per-job directory, parallel runs do not contend on output. This is process-level parallelism over jobs; it does not parallelize the causal-inference computation within a single job.

make-dag-stdin: one job at a time

For external schedulers / cluster queues, make-dag-stdin computes the DAG for a single named job:

$ python -m logdag make-dag-stdin -h
usage: __main__.py make-dag-stdin [-h] [-c CONFIG] [--debug] TASKNAME

make-dag interface for pipeline processing

positional arguments:
  TASKNAME              argument name
  ...

TASKNAME is a jobname from the argument file (e.g. all_21120901_000000). make-dag-stdin reconstructs the single job via jobname2args and runs makedag_main(args, do_dump=True). A typical distributed pattern is:

logdag make-args -c logdag.conf            # write <output_dir>/args
xargs -P 8 -I{} logdag make-dag-stdin -c logdag.conf {} < <output_dir>/args

i.e. fan the jobnames in the argument file out to a job runner that calls make-dag-stdin once per job, giving you scheduling control beyond the in-process -p pool.

test_makedag (the unexposed test_makedag handler) is a smoke entry point: it generates the args, dumps them, and runs makedag_main(am[0]) for the first job only, without do_dump. It is intended for quick checks, not production.

Output layout

All generated artifacts live under [dag] output_dir (default pc_output). For each job (area, window) with jobname <area>_<date>:

<output_dir>/
  args                              # argument file: one jobname per line
  <area>_<date>/
    dag.<ext>                       # the estimated DAG (NetworkX DiGraph)
    evdef.pickle                    # the EventDefinitionMap (eid <-> event def)
  • The DAG file extension/format is [dag] output_dag_format:
    • pickle (default) — pickle.dump of the NetworkX DiGraph -> dag.pickle.
    • jsonnetworkx.node_link_data JSON -> dag.json. (showdag.LogDAG.dump / ArgumentManager.dag_path.)
  • evdef.pickle is the per-job event map dumped during makedag_main; the display/filter commands load it to turn node IDs back into readable event definitions.
  • The per-job directory is created lazily (mkdir, tolerating "already exists") the first time its DAG/evdef path is computed.

Once DAGs are written, load and inspect them with the show-* / plot-* subcommands described in Viewing and Filtering DAGs; the node definitions can be dumped per job with dump-events <jobname>.

Typical workflow summary

  1. Build the feature DB once (logdag.source make-evdb-log-all) — see Data Sources.
  2. (Optional) logdag make-args + logdag show-args to review the planned jobs.
  3. logdag make-dag -p N to estimate all DAGs into <output_dir>/.
  4. Inspect with logdag show-edge-list <jobname>, dump-input, dump-events, and the other CLI Commands.

See also

Clone this wiki locally