-
Notifications
You must be signed in to change notification settings - Fork 3
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 themake-args/make-dag/make-dag-stdin/dump-inputhandlers inlogdag/__main__.py. CLI-houtput and the argument-file example below were produced by running the commands in the logenv venv.
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.confWith the logenv venv the CLI is invoked as python -m logdag:
/Users/sat/project/logenv/.venv/bin/python -m logdag make-dag -c logdag.confThe 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/--confighelp 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.
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.
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_diffEach 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.
[dag] area controls the host grouping each DAG covers:
-
area = all— treat all hosts as a single area namedall(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 viaarea_def.
The list of areas comes from config.getlist(conf, "dag", "area"), so multiple
comma-separated areas multiply the number of jobs.
arguments.ArgumentManager holds the job list and manages its on-disk form:
-
am.generate(arguments.all_args)— populateam.l_argsby callingall_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 viajobname2args. -
am.show()— render the job table used byshow-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.
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_termat load time. If you changeunit_termbetweenmake-argsand a later command that loads the args, the reconstructed windows will use the new length. (Confirmed fromjobname2args, which readsunit_termfromconfrather than from the file.)
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_outputRunning 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_termstepped byunit_diff = 24hproduces 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_HHMMSSform here because the window start carries an explicit time-of-day component.
make-args/show-argsonly 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.
For each job tuple, make-dag calls makedag.makedag_pool ->
makedag.makedag_main(args, do_dump=True). makedag_main orchestrates:
-
Skip-if-exists. If
[dag] pass_dag_exists = trueand the DAG file already exists, the job is skipped (returnsNone). This makes re-runs resumable. -
Build the input via
log2event.makeinput(conf, dt_range, area): load all events for the window from the feature DB (or directly from amulog whenuse_evdb = false), bin them into a time-series matrix, and produce a pandas DataFrame (rows = time bins, columns = event IDs) plus anEventDefinitionMap(the eid <-> event-definition mapping). If there is no data in the window,makeinputreturns(None, None)and the job is skipped. -
Dump the event map (
evmap.dump(args)) so later display/filter commands can resolve node IDs. -
Prior knowledge (
pknowledge.init_prior_knowledge) — optional constraints for the search (see Causal Discovery Methods). -
Estimate the DAG via
makedag.estimate_dag, which dispatches on[dag] cause_algorithmto the appropriate method adapter. -
Persist the result as a
showdag.LogDAG(NetworkXDiGraph+ event map).
makedag_mainreturnsNonefor an empty/skipped job. The production path (make-dag) passesdo_dump=Trueand ignores the return value, so aNoneresult is harmless there. (Documented in themakedag_maindocstring; callers that aggregate over many jobs must skipNone.)
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 ofsequential,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.
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.)
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 amultiprocessing.PoolofNprocesses (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 8Because 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.
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>/argsi.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 unexposedtest_makedaghandler) is a smoke entry point: it generates the args, dumps them, and runsmakedag_main(am[0])for the first job only, withoutdo_dump. It is intended for quick checks, not production.
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.dumpof the NetworkXDiGraph->dag.pickle. -
json—networkx.node_link_dataJSON ->dag.json. (showdag.LogDAG.dump/ArgumentManager.dag_path.)
-
-
evdef.pickleis the per-job event map dumped duringmakedag_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>.
- Build the feature DB once (
logdag.source make-evdb-log-all) — see Data Sources. - (Optional)
logdag make-args+logdag show-argsto review the planned jobs. -
logdag make-dag -p Nto estimate all DAGs into<output_dir>/. - Inspect with
logdag show-edge-list <jobname>,dump-input,dump-events, and the other CLI Commands.
-
Configuration — the config-file model and the
[dag]section. - Configuration Options — every option and default.
-
Causal Discovery Methods —
cause_algorithm,ci_func, and the per-method parameters used inside each job. -
Data Sources — building the feature DB consumed by
make-dag. - Viewing and Filtering DAGs — working with the generated DAG files.
- CLI Commands — the full subcommand reference.