module

Krikri::TimingProfile

Perf item #0 - --timing-profile.

Every other item in that document is an estimate until the run's wall-clock time can actually be attributed to something. This is the attribution: a process-wide set of named buckets, each holding a call count and an accumulated duration, printed as a trailing block after the PLAY RECAP when --timing-profile was passed.

Off by default and, when off, #measure degrades to a bare yield (it is a yielding method, so the compiler inlines the block) - no Time.monotonic call, no Hash lookup, nothing. That matters because the finest-grained bucket here wraps VarSubstitutor#substitute, which a real role calls tens of thousands of times.

Double counting, and how it is avoided

Buckets nest: SSHManager.upload runs a chmod through SSHManager.exec, ConditionalEvaluator.evaluate renders through VarSubstitutor#substitute. Naively summing both would count the inner time twice and make the percentages meaningless.

So every bucket declares a group. Entering a bucket while the same group is already active on this fiber attributes no additional time (and no additional call) - the outermost measurement in a group owns the whole span. Buckets that deliberately want to report a slice of another bucket (the local ssh process spawn inside an ssh exec, say) simply use a different group, and are printed indented as "nested detail, already included above".

Fibers

The re-entrancy guard is keyed per fiber, not globally: with --forks > 1, run_task_for_hosts_in_parallel has one fiber per host, and host B entering transport while host A is parked inside it is genuine concurrency, not re-entrancy. The flip side is that concurrent hosts' spans overlap in wall-clock terms, so bucket totals can legitimately sum to more than the run's wall clock. The report says so rather than pretending otherwise; --forks 1 gives a strictly additive profile.

Constants

CONTROLLER_ROWS = [Row.new("controller.templating", "templating / substitution"), Row.new("controller.conditionals", "conditional evaluation"), Row.new("controller.crinja", "crinja render", 1), Row.new("display.result", "result display")]
PHASE_ROWS = [Row.new("parse.playbook", "playbook + role parse"), Row.new("parse.inventory", "inventory parse"), Row.new("upload.plugins", "plugin upload / staging"), Row.new("execute", "task execution"), Row.new("execute.facts", "fact gathering", 1)]
PHASE_TOTAL_KEYS = ["parse.playbook", "parse.inventory", "upload.plugins", "execute"]

Only these four are non-overlapping and together are meant to cover the run; "unaccounted" below is the wall clock minus their sum (CLI startup, option parsing, recap rendering, and anything else not on a measured path).

TRANSPORT_ROWS = [Row.new("transport.ssh_exec", "ssh exec (fork + wire + remote)"), Row.new("transport.ssh_script", "ssh exec_script (batched / one-shot)"), Row.new("transport.ssh_spawn", "local ssh process spawn", 1), Row.new("transport.daemon_send", "daemon request (pipe round trip)"), Row.new("transport.daemon_batch", "daemon batch request (pipe round trip)"), Row.new("transport.daemon_spawn", "daemon start (ssh + remote exec)", 1), Row.new("transport.scp_upload", "scp upload"), Row.new("transport.scp_download", "scp download"), Row.new("transport.rsync", "rsync upload"), Row.new("transport.local_exec", "local plugin exec (no ssh)")]

Class methods

count(bucket : String) : Int64
Source
disable

Spec-only counterpart to #enable - a run never turns profiling back off, but a spec measuring the disabled path has to.

Source
enable

Called from the CLI when --timing-profile is given. Also (re)starts the wall clock, so the denominator is "time since the flags were parsed" rather than "since the process image loaded".

Source
enabled?
Source
measure(bucket : String, group : String = bucket, &)

Times bucket around the block. See the class comment for what group does; it defaults to the bucket's own name, which is the right choice for any bucket that cannot contain another one.

Source
nanos(bucket : String) : Int64
Source
record_span(bucket : String, elapsed : Time::Span) : Nil

For a span that cannot be expressed as a block (the caller already holds a start and an end).

Source
report(io : IO = STDOUT) : Nil
Source
reset
Source
wall
Source

Nested types