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
completedstamp 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;--sincenarrows the population they are counted over but does not move them.Average lead time
Mean of
completed - createdover 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 asnegative_lead_samples. See "DATA SOURCE".Average cycle time
Mean of
completed - startedover every completed task whose two stamps can carry that measurement: the time from picking the work up to finishing it. karr stampsstartedwhenever it stampscompleted, 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
completedorstartedwas 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_timestampsand named in a note, rather than being dropped silently. Both karr's ownYYYY-MM-DDTHH:MM:SSZand kanban-md's RFC3339 with a numeric offset and optional fraction are understood, as is a bareYYYY-MM-DD.A card whose
startedprecedes its owncreatedis counted there too, and contributes no cycle time. karr wrotestartedas 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
completedprecedes itsstartedis 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:completedwas written asYYYY-MM-DDas well, on more cards thanstartedwas. Untilkarr repairgained 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 tocreatedand 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 withunusable_timestampsat 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:
createdandcompletedare original data in a waystartedis not, so there is no defensible value to clamp a bad completion to.createdis too early,startedasserts 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 asnegative_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, onelabel: valueline per figure, and -- when there are any -- an## Aging Work Itemssection of- id | title | status | age:...lines, the same line shapekarr boarduses. 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.--compactOne
Throughput: ... | Lead: ... | Cycle: ... | Efficiency: ...line, followed by oneAging: #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.--jsonkanban-md's payload --
throughput_7d,throughput_30d,avg_lead_time_hours,avg_cycle_time_hours,flow_efficiency,aging_items-- pluslead_samples,cycle_samples,unusable_timestampsandnegative_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 as0. An average that has no samples is omitted, as it is there.aging_itemsis 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.