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.
This commit is contained in:
parent
0835a2485f
commit
f58d3835b1
@ -15,8 +15,8 @@ module Hledger.Cli.Commands.Stats (
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Control.Monad (when)
|
||||||
import Data.Default (def)
|
import Data.Default (def)
|
||||||
import System.FilePath (takeFileName)
|
|
||||||
import Data.List (intercalate, nub, sortOn)
|
import Data.List (intercalate, nub, sortOn)
|
||||||
import Data.List.Extra (nubSort)
|
import Data.List.Extra (nubSort)
|
||||||
import Data.Map qualified as Map
|
import Data.Map qualified as Map
|
||||||
@ -24,23 +24,27 @@ import Data.Maybe (fromMaybe)
|
|||||||
import Data.HashSet (size, fromList)
|
import Data.HashSet (size, fromList)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Data.Text.Lazy qualified as TL
|
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.Calendar (Day, addDays, diffDays)
|
||||||
import Data.Time.Clock.POSIX (getPOSIXTime)
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||||||
import GHC.Stats
|
import GHC.Stats
|
||||||
|
import GitHash (tGitInfoCwdTry)
|
||||||
import System.Console.CmdArgs.Explicit hiding (Group)
|
import System.Console.CmdArgs.Explicit hiding (Group)
|
||||||
|
import System.FilePath (takeFileName)
|
||||||
import System.Mem (performMajorGC)
|
import System.Mem (performMajorGC)
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
import Text.Tabular.AsciiWide
|
import Text.Tabular.AsciiWide
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Utils (writeOutputLazyText)
|
import Hledger.Cli.Utils (writeOutput)
|
||||||
|
import Hledger.Cli.Version (packageversion, versionStringWith)
|
||||||
|
|
||||||
|
|
||||||
statsmode = hledgerCommandMode
|
statsmode = hledgerCommandMode
|
||||||
$(embedFileRelative "Hledger/Cli/Commands/Stats.txt")
|
$(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."
|
,flagReq ["output-file","o"] (\s opts -> Right $ setopt "output-file" s opts) "FILE" "write output to FILE."
|
||||||
]
|
]
|
||||||
cligeneralflagsgroups1
|
cligeneralflagsgroups1
|
||||||
@ -51,54 +55,75 @@ statsmode = hledgerCommandMode
|
|||||||
-- | Print various statistics for the journal.
|
-- | Print various statistics for the journal.
|
||||||
stats :: CliOpts -> Journal -> IO ()
|
stats :: CliOpts -> Journal -> IO ()
|
||||||
stats opts@CliOpts{rawopts_=rawopts, reportspec_=rspec, progstarttime_} j = do
|
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
|
t <- getPOSIXTime
|
||||||
let dt = t - progstarttime_
|
-- the first lines - general journal stats for one or more periods
|
||||||
rtsStatsEnabled <- getRTSStatsEnabled
|
let
|
||||||
if rtsStatsEnabled
|
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(<SPC><TAB>VALUE[<SPC>DESC])+
|
||||||
|
-- 1.50.99<SPC><TAB>hledger 1.50.99-g0835a2485-20251119, mac-aarch64<SPC><TAB>2025.journal<SPC><TAB>1.99 s elapsed<SPC><TAB>524 txns/s
|
||||||
|
-- 1.50.99<SPC><TAB>hledger 1.50.99-g0835a2485-20251119, mac-aarch64<SPC><TAB>2025.journal<SPC><TAB>1.99 s elapsed<SPC><TAB>524 txns/s<SPC><TAB>788 MB live<SPC><TAB>2172 MB alloc
|
||||||
|
--
|
||||||
|
rtsstats <- getRTSStatsEnabled
|
||||||
|
(maxlivemb, maxinusemb) <- if rtsstats
|
||||||
then do
|
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
|
performMajorGC
|
||||||
RTSStats{..} <- getRTSStats
|
RTSStats{..} <- getRTSStats
|
||||||
printf
|
return (toMegabytes max_live_bytes, toMegabytes max_mem_in_use_bytes)
|
||||||
(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)
|
|
||||||
else
|
else
|
||||||
printf
|
return (0,0)
|
||||||
(intercalate ", "
|
let
|
||||||
["Runtime stats : %.2f s elapsed" -- keep
|
(label, sep)
|
||||||
,"%.0f txns/s"
|
| oneline = (lstrip $ versionStringWith $$tGitInfoCwdTry False "" packageversion <> "\t", "\t")
|
||||||
] ++ "\n(add +RTS -T -RTS for more)\n")
|
| otherwise = ("Runtime stats : ", ", ")
|
||||||
(realToFrac dt :: Float)
|
dt = t - progstarttime_
|
||||||
(fromIntegral numtxns / realToFrac dt :: Float)
|
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
|
toMegabytes n = realToFrac n / 1000000 ::Float -- SI preferred definition, 10^6
|
||||||
-- toMebibytes n = realToFrac n / 1048576 ::Float -- traditional computing definition, 2^20
|
-- 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 =
|
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)
|
,tnum)
|
||||||
where
|
where
|
||||||
showRow (label, val) = Group NoLine $ map (Header . textCell TopLeft)
|
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 = 20 -- keep synced with labels above
|
||||||
-- w = maximum $ map (T.length . fst) stts
|
-- w = maximum $ map (T.length . fst) stts
|
||||||
(stts, tnum) = ([
|
(stts, tnum) = ([
|
||||||
("Main file", path') -- ++ " (from " ++ source ++ ")")
|
("Main file", path' :: String) -- ++ " (from " ++ source ++ ")")
|
||||||
,("Included files", if verbose then unlines includedpaths else show (length includedpaths))
|
,("Included files", if verbose then unlines includedpaths else show (length includedpaths))
|
||||||
,("Txns span", printf "%s to %s (%d days)" (showstart spn) (showend spn) days)
|
,("Txns span", printf "%s to %s (%d days)" (showstart spn) (showend spn) days)
|
||||||
,("Last txn", maybe "none" show lastdate ++ showelapsed lastelapsed)
|
,("Last txn", maybe "none" show lastdate ++ showelapsed lastelapsed)
|
||||||
@ -121,7 +146,7 @@ showLedgerStats verbose l today spn =
|
|||||||
-- Unmarked txns : %(unmarked)s
|
-- Unmarked txns : %(unmarked)s
|
||||||
-- Days since reconciliation : %(reconcileelapsed)s
|
-- Days since reconciliation : %(reconcileelapsed)s
|
||||||
-- Days since last txn : %(recentelapsed)s
|
-- Days since last txn : %(recentelapsed)s
|
||||||
]
|
]
|
||||||
,tnum1)
|
,tnum1)
|
||||||
where
|
where
|
||||||
j = ljournal l
|
j = ljournal l
|
||||||
|
|||||||
@ -4,6 +4,7 @@ Show journal and performance statistics.
|
|||||||
|
|
||||||
```flags
|
```flags
|
||||||
Flags:
|
Flags:
|
||||||
|
-1 show a single line of output
|
||||||
-v --verbose show more detailed output
|
-v --verbose show more detailed output
|
||||||
-o --output-file=FILE write output to FILE.
|
-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),
|
a matched part of it. With a [reporting interval](#reporting-interval),
|
||||||
it shows a report for each report period.
|
it shows a report for each report period.
|
||||||
|
|
||||||
The default output is fairly impersonal, though it reveals the main file name.
|
It also shows some performance statistics:
|
||||||
With `-v/--verbose`, more details are shown, like file paths, included files,
|
|
||||||
and commodity names.
|
|
||||||
|
|
||||||
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
|
By default, the output is reasonably discreet;
|
||||||
- throughput: the number of transactions processed per second
|
it reveals the main file name, your activity level, and the speed of your machine.
|
||||||
- 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.
|
|
||||||
|
|
||||||
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:
|
Example:
|
||||||
|
|
||||||
@ -45,6 +50,11 @@ Market prices : 1000
|
|||||||
Runtime stats : 0.12 s elapsed, 8266 txns/s, 4 MB live, 16 MB alloc
|
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
|
This command supports
|
||||||
the [-o/--output-file](hledger.html#output-destination) option
|
the [-o/--output-file](hledger.html#output-destination) option
|
||||||
(but not [-O/--output-format](hledger.html#output-format)).
|
(but not [-O/--output-format](hledger.html#output-format)).
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user