From f58d3835b154a8e7d600af2063f7e997c365ab08 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Wed, 19 Nov 2025 22:20:22 -1000 Subject: [PATCH] imp:stats: one-line mode; fix -o; doc updates The new -1 flag prints a single line of output in machine-friendly tab-separated format, including the program version, journal file name, and performance stats. Also -o now redirects all output, including the performance stats. --- hledger/Hledger/Cli/Commands/Stats.hs | 113 ++++++++++++++++---------- hledger/Hledger/Cli/Commands/Stats.md | 32 +++++--- 2 files changed, 90 insertions(+), 55 deletions(-) diff --git a/hledger/Hledger/Cli/Commands/Stats.hs b/hledger/Hledger/Cli/Commands/Stats.hs index d834df7fd..6bf04a205 100644 --- a/hledger/Hledger/Cli/Commands/Stats.hs +++ b/hledger/Hledger/Cli/Commands/Stats.hs @@ -15,8 +15,8 @@ module Hledger.Cli.Commands.Stats ( ) where +import Control.Monad (when) import Data.Default (def) -import System.FilePath (takeFileName) import Data.List (intercalate, nub, sortOn) import Data.List.Extra (nubSort) import Data.Map qualified as Map @@ -24,23 +24,27 @@ import Data.Maybe (fromMaybe) import Data.HashSet (size, fromList) import Data.Text qualified as T import Data.Text.Lazy qualified as TL -import Data.Text.Lazy.Builder qualified as TB import Data.Time.Calendar (Day, addDays, diffDays) import Data.Time.Clock.POSIX (getPOSIXTime) import GHC.Stats +import GitHash (tGitInfoCwdTry) import System.Console.CmdArgs.Explicit hiding (Group) +import System.FilePath (takeFileName) import System.Mem (performMajorGC) import Text.Printf (printf) import Text.Tabular.AsciiWide import Hledger import Hledger.Cli.CliOptions -import Hledger.Cli.Utils (writeOutputLazyText) +import Hledger.Cli.Utils (writeOutput) +import Hledger.Cli.Version (packageversion, versionStringWith) statsmode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Stats.txt") - [ flagNone ["verbose","v"] (setboolopt "verbose") "show more detailed output" + [ flagNone ["1"] (setboolopt "") "show a single line of output" + -- Cli.hs converts -1 to --depth=1, no point giving it another name here + , flagNone ["verbose","v"] (setboolopt "verbose") "show more detailed output" ,flagReq ["output-file","o"] (\s opts -> Right $ setopt "output-file" s opts) "FILE" "write output to FILE." ] cligeneralflagsgroups1 @@ -51,54 +55,75 @@ statsmode = hledgerCommandMode -- | Print various statistics for the journal. stats :: CliOpts -> Journal -> IO () stats opts@CliOpts{rawopts_=rawopts, reportspec_=rspec, progstarttime_} j = do - let today = _rsDay rspec - verbose = boolopt "verbose" rawopts - q = _rsQuery rspec - l = ledgerFromJournal q j - intervalspans = snd $ reportSpanBothDates j rspec - ismultiperiod = length intervalspans > 1 - (ls, txncounts) = unzip . map (showLedgerStats verbose l today) $ maybeDayPartitionToDateSpans intervalspans - numtxns = sum txncounts - txt = (if ismultiperiod then id else TL.init) $ TB.toLazyText $ unlinesB ls - writeOutputLazyText opts txt t <- getPOSIXTime - let dt = t - progstarttime_ - rtsStatsEnabled <- getRTSStatsEnabled - if rtsStatsEnabled + -- the first lines - general journal stats for one or more periods + let + today = _rsDay rspec + oneline = intopt "depth" rawopts == 1 + verbose = boolopt "verbose" rawopts + q = _rsQuery rspec + l = ledgerFromJournal q j + intervalspans = snd $ reportSpanBothDates j rspec + ismultiperiod = length intervalspans > 1 + (txts, tnums) = unzip . map (showLedgerStats verbose l today) $ maybeDayPartitionToDateSpans intervalspans + out1 = (if ismultiperiod then id else init) $ unlines txts + + -- the last line - overall performance stats, with memory info if available, + -- in human-friendly or machine-friendly format + -- normal: + -- Runtime stats : 0.14 s elapsed, 7606 txns/s + -- Runtime stats : 0.14 s elapsed, 7606 txns/s, 6 MB live, 18 MB alloc + -- oneline: + -- SHORTVERSION(VALUE[DESC])+ + -- 1.50.99hledger 1.50.99-g0835a2485-20251119, mac-aarch642025.journal1.99 s elapsed524 txns/s + -- 1.50.99hledger 1.50.99-g0835a2485-20251119, mac-aarch642025.journal1.99 s elapsed524 txns/s788 MB live2172 MB alloc + -- + rtsstats <- getRTSStatsEnabled + (maxlivemb, maxinusemb) <- if rtsstats then do - -- do one last GC for most accurate memory stats; probably little effect, hopefully little wasted time + -- do one last garbage collection; probably little effect, hopefully little wasted time performMajorGC RTSStats{..} <- getRTSStats - printf - (intercalate ", " - ["Runtime stats : %.2f s elapsed" -- keep synced - ,"%.0f txns/s" -- - -- ,"%0.0f MB avg live" - ,"%0.0f MB live" - ,"%0.0f MB alloc" - -- ,"(%0.0f MiB" - -- ,"%0.0f MiB)" - ] ++ "\n") - (realToFrac dt :: Float) - (fromIntegral numtxns / realToFrac dt :: Float) - -- (toMegabytes $ fromIntegral cumulative_live_bytes / fromIntegral major_gcs) - (toMegabytes max_live_bytes) - (toMegabytes max_mem_in_use_bytes) + return (toMegabytes max_live_bytes, toMegabytes max_mem_in_use_bytes) else - printf - (intercalate ", " - ["Runtime stats : %.2f s elapsed" -- keep - ,"%.0f txns/s" - ] ++ "\n(add +RTS -T -RTS for more)\n") - (realToFrac dt :: Float) - (fromIntegral numtxns / realToFrac dt :: Float) + return (0,0) + let + (label, sep) + | oneline = (lstrip $ versionStringWith $$tGitInfoCwdTry False "" packageversion <> "\t", "\t") + | otherwise = ("Runtime stats : ", ", ") + dt = t - progstarttime_ + tnum = sum tnums + ss = + [ takeFileName $ journalFilePath j | oneline ] + <> [ + printf "%.2f s elapsed" (realToFrac dt :: Float) + ,printf "%.0f txns/s" (fromIntegral tnum / realToFrac dt :: Float) + ] + <> if rtsstats then [ + printf "%0.0f MB live" maxlivemb + ,printf "%0.0f MB alloc" maxinusemb + -- printf "%0.0f MB avg live" (toMegabytes $ fromIntegral cumulative_live_bytes / fromIntegral major_gcs) + ] + else [ + "(add +RTS -T -RTS for more)" + ] + out2 = label <> intercalate sep ss <> "\n" + + when (not oneline) $ writeOutput opts out1 + when (oneline && debugLevel>0) $ do + let tabstops = intercalate (replicate 7 ' ') (replicate 21 ".") <> "\n" + writeOutput opts tabstops + writeOutput opts $ (if ismultiperiod then "\n" else "") <> out2 toMegabytes n = realToFrac n / 1000000 ::Float -- SI preferred definition, 10^6 -- toMebibytes n = realToFrac n / 1048576 ::Float -- traditional computing definition, 2^20 -showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (TB.Builder, Int) +-- | Generate multiline stats output, possibly verbose, +-- for the given ledger and date period and current date. +-- Also return the number of transactions in the period. +showLedgerStats :: Bool -> Ledger -> Day -> DateSpan -> (String, Int) showLedgerStats verbose l today spn = - (unlinesB $ map (renderRowB def{tableBorders=False, borderSpaces=False} . showRow) stts + (unlines $ map (TL.unpack . renderRow def{tableBorders=False, borderSpaces=False} . showRow) stts ,tnum) where showRow (label, val) = Group NoLine $ map (Header . textCell TopLeft) @@ -106,7 +131,7 @@ showLedgerStats verbose l today spn = w = 20 -- keep synced with labels above -- w = maximum $ map (T.length . fst) stts (stts, tnum) = ([ - ("Main file", path') -- ++ " (from " ++ source ++ ")") + ("Main file", path' :: String) -- ++ " (from " ++ source ++ ")") ,("Included files", if verbose then unlines includedpaths else show (length includedpaths)) ,("Txns span", printf "%s to %s (%d days)" (showstart spn) (showend spn) days) ,("Last txn", maybe "none" show lastdate ++ showelapsed lastelapsed) @@ -121,7 +146,7 @@ showLedgerStats verbose l today spn = -- Unmarked txns : %(unmarked)s -- Days since reconciliation : %(reconcileelapsed)s -- Days since last txn : %(recentelapsed)s - ] + ] ,tnum1) where j = ljournal l diff --git a/hledger/Hledger/Cli/Commands/Stats.md b/hledger/Hledger/Cli/Commands/Stats.md index 2da7a3f36..dddbe794d 100644 --- a/hledger/Hledger/Cli/Commands/Stats.md +++ b/hledger/Hledger/Cli/Commands/Stats.md @@ -4,6 +4,7 @@ Show journal and performance statistics. ```flags Flags: + -1 show a single line of output -v --verbose show more detailed output -o --output-file=FILE write output to FILE. ``` @@ -12,20 +13,24 @@ The stats command shows summary information for the whole journal, or a matched part of it. With a [reporting interval](#reporting-interval), it shows a report for each report period. -The default output is fairly impersonal, though it reveals the main file name. -With `-v/--verbose`, more details are shown, like file paths, included files, -and commodity names. +It also shows some performance statistics: -It also shows some run time statistics: +- how long the program ran for +- the number of transactions processed per second +- the peak live memory in use by the program to do its work +- the peak allocated memory as seen by the program -- elapsed time -- throughput: the number of transactions processed per second -- live: the peak memory in use by the program to do its work -- alloc: the peak memory allocation from the OS as seen by GHC. - Measuring this externally, eg with GNU time, is more accurate; - usually that will be a larger number; sometimes (with swapping?) smaller. +By default, the output is reasonably discreet; +it reveals the main file name, your activity level, and the speed of your machine. -The `stats` command's run time is similar to that of a balance report. +With `-v/--verbose`, more details are shown: +the full paths of all files, and the names of the commodities you work with. + +With `-1`, only one line of output is shown, in a machine-friendly +tab-separated format: the program version, the main journal file name, +and the performance stats, + +The run time of `stats` is similar to that of a balance report. Example: @@ -45,6 +50,11 @@ Market prices : 1000 Runtime stats : 0.12 s elapsed, 8266 txns/s, 4 MB live, 16 MB alloc ``` +```cli +$ hledger stats -1 -f examples/10ktxns-1kaccts.journal +1.50.99-g0835a2485-20251119, mac-aarch64 10ktxns-1kaccts.journal 0.66 s elapsed 15244 txns/s 28 MB live 86 MB alloc +``` + This command supports the [-o/--output-file](hledger.html#output-destination) option (but not [-O/--output-format](hledger.html#output-format)).