NAME

App::karr::Cmd::Metrics - Show flow metrics: throughput, lead/cycle time, flow efficiency, aging work

VERSION

version 0.500

SYNOPSIS

karr metrics
karr metrics --since 2026-01-01
karr metrics --compact
karr metrics --json

DESCRIPTION

Reports the board's flow metrics: how much work finished lately, how long it took, how much of that time the work was actually started, and what has been in flight too long. Archived tasks are excluded from every figure, the way they are excluded from list and board.

Like the other read commands (board, list, show, log, context) this one does not sync first, and it refuses a repository that holds no board rather than reporting zeroes for it.

METRICS

Every number below is derived from the lifecycle stamps on the cards -- created, started, completed -- and from nothing else. See "DATA SOURCE" for why, and for what that costs.

  • Throughput 7d / 30d

    How many tasks carry a completed stamp inside the last 7 and 30 days. The windows are fixed, as they are in kanban-md, so the two figures mean the same thing on both tools; --since narrows the population they are counted over but does not move them.

  • Average lead time

    Mean of completed - created over every completed task: the whole time a request existed, queue time included. Never clamped, and so never quite a measurement on a board old enough to hold day-granular stamps -- how many of its samples are impossible is reported alongside it as negative_lead_samples. See "DATA SOURCE".

  • Average cycle time

    Mean of completed - started over every completed task whose two stamps can carry that measurement: the time from picking the work up to finishing it. karr stamps started whenever it stamps completed, even for a card dragged straight into a terminal status (see "update_timestamps" in App::karr::Task), so on a board written by a current karr the two populations are the same set. They differ where a card was imported without a start, carries one that precedes its own creation, or carries a completion that precedes that start (see "DATA SOURCE").

  • Flow efficiency

    Cycle time as a fraction of lead time -- how much of a task's life was spent being worked on rather than waiting. Computed over the tasks that contributed to both averages, and over their own lead time rather than the board's: that is the one deliberate departure from kanban-md, which divides the average cycle time by the average lead time even when the two averages were taken over different populations. Where every completed card carries a usable start, the two definitions produce the same number; where they do not, kanban-md's can report an efficiency above 100%, which describes nothing.

  • Aging work items

    Tasks that were started, are not in a terminal status, and carry no completion, with the time since started. Listed oldest first (karr's refs hand tasks back in no particular order, so an explicit sort is needed where kanban-md could lean on its file order).

An average over no tasks is reported as -- rather than as 0, and the default and compact renderings state how many tasks each average was taken over, so a figure resting on two cards cannot be mistaken for one resting on two hundred. The lead average is not clamped: a card whose completed precedes its created -- reachable by hand-editing, by another tool, or by a pre-0.403 karr that stamped completed as a bare date -- contributes a negative duration and is left visible as one, because a metrics command that quietly normalises impossible data is how impossible data survives. What it does instead is say how much of the average rests on such samples: negative_lead_samples counts them, and the closing note of the other two renderings states the same figure, so the number is both left standing and readable. The cycle average is the one figure that impossible data is kept out of rather than left in: a card whose stamps cannot order into a duration is excluded and counted in unusable_timestamps instead, which makes it visible by the other route the command has. Neither figure is ever silently normalised; see "DATA SOURCE" for which cards that costs, and how many.

DATA SOURCE

The lifecycle stamps are the only source. Every status change goes through "apply_status_change" in App::karr::Role::TaskMutation or karr pick, and both stamp started and completed through the board's own config, so a board karr wrote carries the timestamps these metrics need on the cards themselves.

The activity log (refs/karr/log/*, App::karr::Cmd::Log) is deliberately not consulted. It is richer -- it dates individual writes -- but it only exists for boards that were active after it was introduced, its entries record the status a write left a task in rather than the transition it made, and the bulk paths (import, restore, repair) write refs without logging. A cycle time reconstructed from it would be silently short on exactly the boards whose history is oldest, and would disagree with the stamps on the cards.

Five limitations follow from that choice, and all five are visible in the output rather than hidden:

  • A card whose completed or started was never stamped -- created directly into a terminal status, or moved by a karr old enough to miss the stamp on a board with non-default statuses -- is missing from the averages. The per-average task counts are what expose it.

  • A card carrying a timestamp that cannot be parsed at all (hand-edited, or written by a third tool in an unexpected format) is counted in unusable_timestamps and named in a note, rather than being dropped silently. Both karr's own YYYY-MM-DDTHH:MM:SSZ and kanban-md's RFC3339 with a numeric offset and optional fraction are understood, as is a bare YYYY-MM-DD.

  • A card whose started precedes its own created is counted there too, and contributes no cycle time. karr wrote started as a bare date until ticket #68, and such a stamp reads as midnight -- earlier than a card created later the same day, which is the normal case for a ticket picked up on the day it was filed. karr's own board held 75 of them among 116 finished tickets when this command was written, and counting them made its average cycle time exceed its average lead time -- a flow efficiency of 107.3%. The start is still good enough to say the work is in flight, so the aging list below uses it.

  • A card whose completed precedes its started is counted there too, and contributes no cycle time either -- a cycle time may not be negative. This is the same pre-#68 bare date on the other stamp: completed was written as YYYY-MM-DD as well, on more cards than started was. Until karr repair gained its start clamp (ticket #138) such a card was usually already out of the cycle average through the check above, because its start was impossible too; the clamp raises that start to created and leaves the bare-date completion below it, so the ordering fault moves from the start to the cycle. On karr's own board that turned 42 of 117 cycle samples negative and reported an average cycle time of 16 minutes with unusable_timestamps at zero -- the figure whose job is to say what is missing, saying nothing (ticket #140).

  • The lead time of that same card is still computed, and can still be negative: created and completed are original data in a way started is not, so there is no defensible value to clamp a bad completion to. created is too early, started asserts a cycle time of zero, and the end of the day the bare date bounds is an invention. So the impossible sample stays in the average, and the count of such samples is reported next to it as negative_lead_samples (ticket #139).

The honest answer about the hour

A board written before ticket #68 (karr 0.403) carries day-granular started and completed stamps: bare YYYY-MM-DD, which everything here reads as midnight UTC. That is the single cause behind the three ordering faults above -- a card filed at 15:49 and finished the same day carries a completion stamped nearly sixteen hours before its own creation, and the duration computed from it is negative for no reason but the missing clock.

On such a board the averages are not wrong so much as finer than what they rest on. An average lead time printed as 6h 39m claims a precision the underlying stamps never had, and it is dragged down by every sample the day-granularity made negative: on karr's own board, when this was written, 51 of 138 lead samples were negative, averaging -10.1 hours, and discarding them would have raised the printed average from 6h 39m to 16h 28m -- more than double. Neither of those is the truth: the first is what the board says, the second is what it would say with the evidence removed. karr prints the first and states the 51, because the honest report of day-granular history is the figure plus how much of it cannot be believed, not a tidier figure arrived at by dropping the inconvenient cards (ticket #139).

The two counters, and why they are two

unusable_timestamps counts cards, not stamps. It is the number of tasks -- after --since, and never counting archived ones -- that carry at least one lifecycle stamp karr could not use, and that are missing from at least one of the two averages for that reason; a card with two such stamps counts once. A card that is merely incomplete is not counted there: one with no started at all has nothing unusable about it, contributes no cycle time, and is already accounted for by the difference between the two per-average sample counts.

negative_lead_samples counts the opposite case: cards that are in the lead average and should not be believed. It is the number of tasks -- same population, after --since and never archived -- whose completed precedes their own created, so the lead time they contribute is negative. Every one of them is included in lead_samples, and negative_lead_samples is therefore never larger than it.

The two are deliberately not summed, and one card can be in both -- a card with an unreadable started and a completion below its own creation is missing from the cycle average and impossible in the lead one. They answer different questions: how much the averages could not see, and how much of what they did see cannot be true. Folding the negative lead samples into unusable_timestamps would break that counter's definition -- those cards are missing from nothing -- and would leave the lead average with no caveat of its own again.

OUTPUT MODES

  • Default output

    A Markdown-flavoured plaintext block: # Flow Metrics, one label: value line per figure, and -- when there are any -- an ## Aging Work Items section of - id | title | status | age:... lines, the same line shape karr board uses. Below that, one closing note per counter that is not zero: what the lead average cannot be believed on, and what the averages left out.

  • --compact

    One Throughput: ... | Lead: ... | Cycle: ... | Efficiency: ... line, followed by one Aging: #id [status] title (age) line per aging item, as in kanban-md's compact rendering. The same closing notes follow, because a caveat dropped for brevity is a caveat the reader never had.

  • --json

    kanban-md's payload -- throughput_7d, throughput_30d, avg_lead_time_hours, avg_cycle_time_hours, flow_efficiency, aging_items -- plus lead_samples, cycle_samples, unusable_timestamps and negative_lead_samples, so a consumer can tell a real zero from an empty population, and a believable average from one built on impossible stamps. The two counters carry exactly the figures the closing notes of the other two renderings state, under the definitions above; both are always present, including as 0. An average that has no samples is omitted, as it is there. aging_items is always present, as an empty array when nothing is aging.

SEE ALSO

karr, App::karr, App::karr::Cmd::Board, App::karr::Cmd::List, App::karr::Cmd::Context, App::karr::Cmd::Log, App::karr::Task

since

karr metrics --since 2026-01-01

Drops every task completed on or before this date from the figures, so the averages describe a chosen period instead of the whole board's history. Tasks that are not completed at all are kept, so the aging list is unaffected -- kanban-md's --since works the same way. The date is validated the way every other date in karr is ("validate_due" in App::karr::Config): calendar-correct YYYY-MM-DD, and a usage error otherwise.

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/karr/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.