diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,22 @@
+0.14
+===
+* Added charting
+* Added Perf.Chart
+* refactored reportMain
+* expanded ReportOptions
+* added more algorithms to Example
+* refactored Perf.BigO
+* added PerfDumpOptions
+* reportOrg2D ==> report2D
+* added reportBigO
+* added reportTasty'
+* added reportToConsole & parseClock
+* refactored SpaceStats
+* added tickIOWith, timesN & timesNWith
+* removed measureM from Measure
+* added multiN
+
+
 0.13
 ===
 * replaced rdtsc with clocks library.
diff --git a/app/bench.hs b/app/bench.hs
--- a/app/bench.hs
+++ b/app/bench.hs
@@ -9,4 +9,4 @@
 main = do
   let l = 1000
   let a = ExampleSum
-  reportMain (List.intercalate "-" [show a, show l]) $ testExample (examplePattern a l)
+  reportMain a defaultReportOptions (List.intercalate "-" [show a, show @Int l])  (testExample . examplePattern a)
diff --git a/app/explore.hs b/app/explore.hs
--- a/app/explore.hs
+++ b/app/explore.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 -- | basic measurement and callibration
@@ -6,58 +7,59 @@
 import Control.DeepSeq
 import Control.Monad
 import Control.Monad.State.Lazy
-import Data.FormatN
-import Data.List (intercalate, nub)
+import Data.List (intercalate)
 import Data.Map.Strict qualified as Map
 import Data.Text (Text)
 import Data.Text qualified as Text
 import Data.Text.IO qualified as Text
+import GHC.Exts
+import GHC.Generics
+import Optics.Core
 import Options.Applicative
+import Options.Applicative.Help.Pretty
 import Perf
 import Prelude
 
-data RunType = RunExample | RunExamples | RunClocks | RunNub | RunExampleIO | RunSums | RunLengths | RunNoOps | RunTicks deriving (Eq, Show)
+data Run = RunExample | RunExampleIO | RunSums | RunLengths | RunNoOps | RunTicks deriving (Eq, Show)
 
-data ExploreOptions = ExploreOptions
-  { exploreReportOptions :: ReportOptions,
-    exploreRun :: RunType,
-    exploreLength :: Int,
-    exploreExample :: Example
+data AppConfig = AppConfig
+  { appRun :: Run,
+    appExample :: Example,
+    appReportOptions :: ReportOptions
   }
-  deriving (Eq, Show)
+  deriving (Eq, Show, Generic)
 
-parseRun :: Parser RunType
+defaultAppConfig :: AppConfig
+defaultAppConfig = AppConfig RunExample ExampleSum defaultReportOptions
+
+parseRun :: Parser Run
 parseRun =
   flag' RunSums (long "sums" <> help "run on sum algorithms")
     <|> flag' RunLengths (long "lengths" <> help "run on length algorithms")
-    <|> flag' RunNub (long "nub" <> help "nub test")
-    <|> flag' RunClocks (long "clocks" <> help "clock test")
-    <|> flag' RunExamples (long "examples" <> help "run on example algorithms")
-    <|> flag' RunExample (long "example" <> help "run on the example algorithm")
+    <|> flag' RunExample (long "example" <> help "run on the example algorithm" <> style (annotate bold))
     <|> flag' RunExampleIO (long "exampleIO" <> help "exampleIO test")
     <|> flag' RunNoOps (long "noops" <> help "noops test")
     <|> flag' RunTicks (long "ticks" <> help "tick test")
     <|> pure RunExample
 
-exploreOptions :: Parser ExploreOptions
-exploreOptions =
-  ExploreOptions
-    <$> parseReportOptions
-    <*> parseRun
-    <*> option auto (value 1000 <> long "length" <> short 'l' <> help "length of list")
+appParser :: AppConfig -> Parser AppConfig
+appParser def =
+  AppConfig
+    <$> parseRun
     <*> parseExample
+    <*> parseReportOptions (view #appReportOptions def)
 
-exploreOpts :: ParserInfo ExploreOptions
-exploreOpts =
+appConfig :: AppConfig -> ParserInfo AppConfig
+appConfig def =
   info
-    (exploreOptions <**> helper)
-    (fullDesc <> progDesc "perf exploration" <> header "examples of perf usage")
+    (appParser def <**> helper)
+    (fullDesc <> header "Examples of perf usage (defaults in bold)")
 
 -- | * exampleIO
 exampleIO :: (Semigroup t) => PerfT IO t ()
 exampleIO = do
   txt <- fam "file-read" (Text.readFile "src/Perf.hs")
-  n <- fap "length" Text.length txt
+  n <- ffap "length" Text.length txt
   fam "print-result" (Text.putStrLn $ "length of file is: " <> Text.pack (show n))
 
 -- | * sums
@@ -71,6 +73,7 @@
   addStat [l, "tickForceArgs"] . statD s . fmap fromIntegral =<< lift (fst <$> multi tickForceArgs n f a)
   addStat [l, "stepTime"] . statD s . fmap fromIntegral =<< lift (mconcat . fmap snd . take 1 . Map.toList <$> execPerfT (toMeasureN n stepTime) (f |$| a))
   addStat [l, "times"] . statD s . fmap fromIntegral =<< lift (mconcat . fmap snd . take 1 . Map.toList <$> execPerfT (times n) (f |$| a))
+  addStat [l, "timesn"] . statD s . fmap fromIntegral =<< lift (mconcat . fmap snd . Map.toList <$> execPerfT (pure <$> timesN n) (f |$| a))
 
 statTicksSum :: (NFData b, Enum b, Num b) => SumPattern b -> Int -> StatDType -> StateT (Map.Map [Text] Double) IO ()
 statTicksSum (SumFuse label f a) n s = statTicks label f a n s
@@ -92,32 +95,23 @@
 
 main :: IO ()
 main = do
-  o <- execParser exploreOpts
-  let repOptions = exploreReportOptions o
+  o <- execParser (appConfig defaultAppConfig)
+  let repOptions = appReportOptions o
   let n = reportN repOptions
   let s = reportStatDType repOptions
   let mt = reportMeasureType repOptions
   let c = reportClock repOptions
-  let !l = exploreLength o
-  let a = exploreExample o
-  let r = exploreRun o
+  let !l = reportLength repOptions
+  let a = appExample o
+  let r = appRun o
 
   case r of
-    RunNub -> do
-      reportMainWith repOptions (show r) (ffap "nub" nub [1 .. l])
     RunExample -> do
-      reportMainWith repOptions (intercalate "-" [show r, show a, show l]) $
-        testExample (examplePattern a l)
-    RunExamples -> do
-      reportMainWith
-        repOptions
-        (intercalate "-" [show r, show l])
-        (statExamples l)
-    RunClocks -> do
-      reportMainWith
+      reportMain
+        a
         repOptions
-        (intercalate "-" [show r, show a, show l, show c])
-        (testExample (examplePattern a l))
+        (intercalate "-" [show r, show a, show l])
+        (testExample . examplePattern a)
     RunExampleIO -> do
       m1 <- execPerfT (measureDs mt c 1) exampleIO
       (_, (m', m2)) <- outer "outer-total" (measureDs mt c 1) (measureDs mt c 1) exampleIO
@@ -139,4 +133,4 @@
       report o' (allStats 4 (Map.mapKeys (: []) m))
     RunTicks -> do
       m <- statTicksSums n l s
-      reportOrg2D (fmap (expt (Just 3)) m)
+      report2D m
diff --git a/perf.cabal b/perf.cabal
--- a/perf.cabal
+++ b/perf.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: perf
-version: 0.13.0.0
+version: 0.14.0.0
 license: BSD-3-Clause
 license-file: LICENSE
 copyright: Tony Day (c) 2018
@@ -17,9 +17,8 @@
 build-type: Simple
 tested-with:
     , GHC == 9.10.1
-    , GHC == 9.8.2
     , GHC == 9.6.5
-
+    , GHC == 9.8.2
 extra-doc-files:
     ChangeLog.md
     readme.md
@@ -45,6 +44,7 @@
         -Wincomplete-uni-patterns
         -Wpartial-fields
         -Wredundant-constraints
+        -fproc-alignment=64
 
 common ghc2021-stanza
     default-language: GHC2021
@@ -54,21 +54,29 @@
     import: ghc2021-stanza
     hs-source-dirs: src
     build-depends:
-        , base                 >=4.7 && <5
+        , base                 >=4.14 && <5
+        , boxes                >=0.1.5 && <0.2
+        , chart-svg            >=0.7 && <0.9
         , clock                >=0.8 && <0.9
         , containers           >=0.6 && <0.8
         , deepseq              >=1.4.4 && <1.6
         , formatn              >=0.2.1 && <0.4
         , mtl                  >=2.2.2 && <2.4
-        , numhask-space        >=0.10 && <0.12
-        , optparse-applicative >=0.17 && <0.19
+        , numhask-space        >=0.10 && <0.13
+        , optics-core          >=0.4.1 && <0.5
+        , optparse-applicative >=0.17 && <0.20
+        , prettychart          >=0.3 && <0.4
+        , prettyprinter        >=1.7.1 && <1.8
         , recursion-schemes    >=5.2.2 && <5.3
+        , tasty                >=1.5.2 && <1.6
+        , tasty-bench          >=0.4 && <0.5
         , text                 >=1.2 && <2.2
         , vector               >=0.12.3 && <0.14
     exposed-modules:
         Perf
         Perf.Algos
         Perf.BigO
+        Perf.Chart
         Perf.Count
         Perf.Measure
         Perf.Report
@@ -85,13 +93,12 @@
     main-is: explore.hs
     hs-source-dirs: app
     build-depends:
-        , base                 >=4.7 && <5
-        , clock                >=0.8 && <0.9
+        , base                 >=4.14 && <5
         , containers           >=0.6 && <0.8
         , deepseq              >=1.4.4 && <1.6
-        , formatn              >=0.2.1 && <0.4
         , mtl                  >=2.2.2 && <2.4
-        , optparse-applicative >=0.17 && <0.19
+        , optics-core          >=0.4.1 && <0.5
+        , optparse-applicative >=0.17 && <0.20
         , perf
         , text                 >=1.2 && <2.2
     ghc-options: -O2
@@ -103,7 +110,7 @@
     main-is: bench.hs
     hs-source-dirs: app
     build-depends:
-        , base >=4.7 && <5
+        , base >=4.14 && <5
         , perf
     ghc-options: -O2
     type: exitcode-stdio-1.0
diff --git a/readme.md b/readme.md
--- a/readme.md
+++ b/readme.md
@@ -1,37 +1,7 @@
 
-# Table of Contents
-
-1.  [Introduction](#orga196864)
-2.  [Setup](#orgfd30244)
-3.  [System.Clock](#orgb51effe)
-    1.  [resolution](#org7dcd69d)
-4.  [Time](#org6c19f0f)
-    1.  [What is a tick?](#org49fb855)
-    2.  [tick\_](#org1de7ebb)
-    3.  [multiple ticks](#org27958a7)
-    4.  [tickIO](#orga206cb6)
-    5.  [sum example](#orgd6c8625)
-5.  [PerfT](#org0974e4d)
-6.  [perf-explore](#org216f105)
-    1.  [noops](#org2d42223)
-    2.  [measurement context](#org95a9062)
-        1.  [short list](#orgdb37d7c)
-        2.  [long list](#org56b0098)
-    3.  [sums](#orgff01033)
-    4.  [lengths](#org5abd0c1)
-    5.  [Space](#org01bde6f)
-7.  [Perf.BigO](#org753786d)
-8.  [References](#org47311bd)
-    1.  [Core](#orgadd7f60)
-    2.  [Profiling](#org31e588b)
-        1.  [setup](#orgf72792c)
-        2.  [Space usage output (-s)](#org1d9ca37)
-        3.  [Cost center profile (-p)](#orgeb93acc)
-        4.  [heap analysis (-hc -l)](#org76c2a10)
-    3.  [Cache speed](#org3a53ed0)
-
-[![img](https://img.shields.io/hackage/v/perf.svg)](https://hackage.haskell.org/package/perf) [![img](https://github.com/tonyday567/perf/workflows/haskell-ci/badge.svg)](https://github.com/tonyday567/perf/actions?query=workflow%3Ahaskell-ci)
+# perf
 
+[![img](https://img.shields.io/hackage/v/perf.svg)](https://hackage.haskell.org/package/perf) [![img](https://github.com/tonyday567/perf/workflows/haskell-ci.yml/badge.svg)](https://github.com/tonyday567/perf/actions)
 
 <a id="orga196864"></a>
 
diff --git a/src/Perf/Algos.hs b/src/Perf/Algos.hs
--- a/src/Perf/Algos.hs
+++ b/src/Perf/Algos.hs
@@ -12,13 +12,12 @@
 module Perf.Algos
   ( -- * command-line options
     Example (..),
-    allExamples,
     parseExample,
     ExamplePattern (..),
     examplePattern,
     exampleLabel,
     testExample,
-    statExamples,
+    tastyExample,
 
     -- * sum algorithms
     SumPattern (..),
@@ -85,34 +84,28 @@
 import Data.Bifunctor
 import Data.Foldable
 import Data.Functor.Foldable
+import Data.List qualified as List
 import Data.Map.Strict qualified as Map
 import Data.Text (Text)
 import Options.Applicative
+import Options.Applicative.Help.Pretty
 import Perf.Types
+import Test.Tasty.Bench
 
 -- | Algorithm examples for testing
-data Example = ExampleSumFuse | ExampleSum | ExampleLengthF | ExampleConstFuse | ExampleMapInc | ExampleNoOp deriving (Eq, Show)
-
--- | All the example algorithms.
-allExamples :: [Example]
-allExamples =
-  [ ExampleSumFuse,
-    ExampleSum,
-    ExampleLengthF,
-    ExampleConstFuse,
-    ExampleMapInc,
-    ExampleNoOp
-  ]
+data Example = ExampleSumFuse | ExampleSum | ExampleLengthF | ExampleConstFuse | ExampleMapInc | ExampleNoOp | ExampleNub | ExampleFib deriving (Eq, Show)
 
 -- | Parse command-line options for algorithm examples.
 parseExample :: Parser Example
 parseExample =
   flag' ExampleSumFuse (long "sumFuse" <> help "fused sum pipeline")
-    <|> flag' ExampleSum (long "sum" <> help "sum")
+    <|> flag' ExampleSum (long "sum" <> style (annotate bold) <> help "sum")
     <|> flag' ExampleLengthF (long "lengthF" <> help "foldr id length")
     <|> flag' ExampleConstFuse (long "constFuse" <> help "fused const pipeline")
     <|> flag' ExampleMapInc (long "mapInc" <> help "fmap (+1)")
     <|> flag' ExampleNoOp (long "noOp" <> help "const ()")
+    <|> flag' ExampleFib (long "fib" <> help "fibonacci")
+    <|> flag' ExampleNub (long "nub" <> help "List.nub")
     <|> pure ExampleSum
 
 -- | Unification of example function applications
@@ -123,6 +116,8 @@
   | PatternConstFuse Text (Int -> ()) Int
   | PatternMapInc Text ([Int] -> [Int]) [Int]
   | PatternNoOp Text (() -> ()) ()
+  | PatternNub Text ([Int] -> [Int]) [Int]
+  | PatternFib Text (Int -> Integer) Int
 
 -- | Labels
 exampleLabel :: ExamplePattern a -> Text
@@ -132,6 +127,8 @@
 exampleLabel (PatternConstFuse l _ _) = l
 exampleLabel (PatternMapInc l _ _) = l
 exampleLabel (PatternNoOp l _ _) = l
+exampleLabel (PatternNub l _ _) = l
+exampleLabel (PatternFib l _ _) = l
 
 -- | Convert an 'Example' to an 'ExamplePattern'.
 examplePattern :: Example -> Int -> ExamplePattern Int
@@ -141,19 +138,30 @@
 examplePattern ExampleConstFuse l = PatternConstFuse "constFuse" constFuse l
 examplePattern ExampleMapInc l = PatternMapInc "mapInc" mapInc [1 .. l]
 examplePattern ExampleNoOp _ = PatternNoOp "noop" (const ()) ()
+examplePattern ExampleNub l = PatternNub "nub" List.nub [1 .. l]
+examplePattern ExampleFib l = PatternFib "fib" fib l
 
 -- | Convert an 'ExamplePattern' to a 'PerfT'.
 testExample :: (Semigroup a, MonadIO m) => ExamplePattern Int -> PerfT m a ()
-testExample (PatternSumFuse label f a) = void $ fap label f a
-testExample (PatternSum label f a) = void $ fap label f a
-testExample (PatternLengthF label f a) = void $ fap label f a
-testExample (PatternConstFuse label f a) = void $ fap label f a
-testExample (PatternMapInc label f a) = void $ fap label f a
-testExample (PatternNoOp label f a) = void $ fap label f a
+testExample (PatternSumFuse label f a) = void $ ffap label f a
+testExample (PatternSum label f a) = void $ ffap label f a
+testExample (PatternLengthF label f a) = void $ ffap label f a
+testExample (PatternConstFuse label f a) = void $ ffap label f a
+testExample (PatternMapInc label f a) = void $ ffap label f a
+testExample (PatternNoOp label f a) = void $ ffap label f a
+testExample (PatternNub label f a) = void $ ffap label f a
+testExample (PatternFib label f a) = void $ ffap label f a
 
--- | run an example measurement.
-statExamples :: (Semigroup a, MonadIO m) => Int -> PerfT m a ()
-statExamples l = mapM_ testExample ((`examplePattern` l) <$> allExamples)
+-- | Convert an 'ExamplePattern' to a tasty-bench run.
+tastyExample :: ExamplePattern Int -> Benchmarkable
+tastyExample (PatternSumFuse _ f a) = nf f a
+tastyExample (PatternSum _ f a) = nf f a
+tastyExample (PatternLengthF _ f a) = nf f a
+tastyExample (PatternConstFuse _ f a) = nf f a
+tastyExample (PatternMapInc _ f a) = nf f a
+tastyExample (PatternNoOp _ f a) = nf f a
+tastyExample (PatternNub _ f a) = nf f a
+tastyExample (PatternFib _ f a) = nf f a
 
 -- | Unification of sum function applications
 data SumPattern a
@@ -477,3 +485,7 @@
   where
     go (y : ys) (_ : _ : zs) = first (y :) (go ys zs)
     go ys _ = ([], ys)
+
+-- | Fibonnacci
+fib :: Int -> Integer
+fib n = if n < 2 then toInteger n else fib (n - 1) + fib (n - 2)
diff --git a/src/Perf/BigO.hs b/src/Perf/BigO.hs
--- a/src/Perf/BigO.hs
+++ b/src/Perf/BigO.hs
@@ -17,27 +17,26 @@
     fromOrder,
     toOrder,
     order,
-    mcurve,
-    dcurve,
-    tcurve,
     diffs,
     bestO,
     estO,
     estOs,
-    estOrder,
+    makeNs,
+    OrderOptions (..),
+    defaultOrderOptions,
+    parseOrderOptions,
   )
 where
 
 import Data.Bool
+import Data.FormatN
 import Data.List qualified as List
-import Data.Map.Strict qualified as Map
 import Data.Maybe
 import Data.Monoid
 import Data.Vector qualified as V
 import GHC.Generics
-import Perf.Stats
-import Perf.Time
-import Perf.Types
+import Options.Applicative
+import Prettyprinter
 import Prelude
 
 -- $setup
@@ -181,26 +180,28 @@
   fromInteger x = Order $ replicate 9 (fromInteger x)
 
 -- | A set of factors consisting of the dominant order, the dominant order factor and a constant factor
-data BigOrder a = BigOrder {bigOrder :: O, bigFactor :: a, bigConstant :: a} deriving (Eq, Ord, Show, Generic, Functor)
+data BigOrder a = BigOrder {bigOrder :: O, bigFactor :: a} deriving (Eq, Ord, Show, Generic, Functor)
 
+instance Pretty (BigOrder Double) where
+  pretty (BigOrder o f) = pretty (decimal (Just 2) f) <> " * O(" <> viaShow o <> ")"
+
 -- | compute the BigOrder
 --
 -- >>> fromOrder o
--- BigOrder {bigOrder = N2, bigFactor = 1.0, bigConstant = 100.0}
+-- BigOrder {bigOrder = N2, bigFactor = 1.0}
 fromOrder :: Order Double -> BigOrder Double
-fromOrder o' = BigOrder o f r
+fromOrder o' = BigOrder o f
   where
     (o, f) = bigO o'
-    r = runtime o'
 
 -- | convert a BigOrder to an Order.
 --
 -- toOrder . fromOrder is not a round trip iso.
 --
 -- >>> toOrder (fromOrder o)
--- Order {factors = [0.0,1.0,0.0,0.0,0.0,0.0,0.0,100.0]}
+-- Order {factors = [0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0]}
 toOrder :: BigOrder Double -> Order Double
-toOrder (BigOrder o f r) = order o f + order N0 r
+toOrder (BigOrder o f) = order o f
 
 -- | The factor for each O given an n, and a measurement.
 --
@@ -249,26 +250,26 @@
     go os _ [m] = os <> [order N0 m]
     go os ns' ms' = let (o', res) = estO ns' ms' in go (os <> [o']) (List.init ns') (List.init res)
 
--- | performance curve for a Measure.
-mcurve :: (Semigroup a) => Measure IO a -> (Int -> b) -> [Int] -> IO [a]
-mcurve m f ns = mapM (\n -> (Map.! "") <$> execPerfT m (f |$| n)) ns
+makeNs :: Int -> Double -> Int -> [Int]
+makeNs n0 d low = reverse $ go (next n0) [n0]
+  where
+    next n = floor (fromIntegral n / d)
+    go :: Int -> [Int] -> [Int]
+    go n acc = bool (go (next n) (acc <> [n])) acc (low >= n)
 
--- | repetitive Double Meaure performance curve.
-dcurve :: (Int -> Measure IO [Double]) -> StatDType -> Int -> (Int -> a) -> [Int] -> IO [Double]
-dcurve m s sims f ns = fmap getSum <$> mcurve (Sum . statD s <$> m sims) f ns
+data OrderOptions = OrderOptions
+  { doOrder :: Bool,
+    orderLow :: Int,
+    orderDivisor :: Double
+  }
+  deriving (Eq, Show, Generic)
 
--- | time performance curve.
-tcurve :: StatDType -> Int -> (Int -> a) -> [Int] -> IO [Double]
-tcurve = dcurve (fmap (fmap fromIntegral) . times)
+defaultOrderOptions :: OrderOptions
+defaultOrderOptions = OrderOptions False 10 9
 
--- | BigOrder estimate
---
--- > estOrder (\x -> sum [1..x]) 100 [1,10,100,1000,10000]
--- > BigOrder {bigOrder = N1, bigFactor = 76.27652961460446, bigConstant = 0.0}
---
--- > estOrder (\x -> sum $ nub [1..x]) 100 [1,10,100,1000]
--- > BigOrder {bigOrder = N2, bigFactor = 13.485763594353541, bigConstant = 0.0}
-estOrder :: (Int -> b) -> Int -> [Int] -> IO (BigOrder Double)
-estOrder f sims ns = do
-  xs <- tcurve StatBest sims f ns
-  pure $ fromOrder $ fst $ estO (fromIntegral <$> ns) xs
+parseOrderOptions :: OrderOptions -> Parser OrderOptions
+parseOrderOptions def =
+  OrderOptions
+    <$> switch (long "order" <> short 'o' <> help "calculate order")
+    <*> option auto (value (orderLow def) <> long "orderlowest" <> showDefaultWith show <> metavar "DOUBLE" <> help "smallest order test")
+    <*> option auto (value (orderDivisor def) <> long "orderdivisor" <> showDefaultWith show <> metavar "DOUBLE" <> help "divisor for order computation")
diff --git a/src/Perf/Chart.hs b/src/Perf/Chart.hs
new file mode 100644
--- /dev/null
+++ b/src/Perf/Chart.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Perf.Chart where
+
+import Chart
+import Control.Category ((>>>))
+import Data.Bifunctor
+import Data.Bool
+import Data.List qualified as List
+import Data.Map.Strict qualified as Map
+import Data.Maybe
+import Data.Text (Text)
+import Data.Text qualified as Text
+import GHC.Generics
+import Optics.Core
+import Options.Applicative
+import Perf.Stats as Perf
+import Prettychart
+
+--     m <- fromDump defaultPerfDumpOptions
+data PerfChartOptions
+  = PerfChartOptions
+  { doChart :: Bool,
+    chartFilepath :: FilePath,
+    truncateAt :: Double,
+    doSmallChart :: Bool,
+    doBigChart :: Bool,
+    doHistChart :: Bool,
+    doAveragesLegend :: Bool,
+    averagesStyle :: Style,
+    averagesPaletteStart :: Int,
+    averagesLegend :: LegendOptions,
+    smallStyle :: Style,
+    smallHud :: HudOptions,
+    bigStyle :: Style,
+    bigHud :: HudOptions,
+    titleSize :: Double,
+    histGrain :: Int,
+    bigWidth :: Double,
+    excludeZeros :: Bool
+  }
+  deriving (Eq, Show, Generic)
+
+defaultPerfChartOptions :: PerfChartOptions
+defaultPerfChartOptions = PerfChartOptions False "other/perf.svg" 10 True True True True (defaultGlyphStyle & set #size 0.05) 2 (defaultLegendOptions & set #place PlaceBottom & set #numStacks 3 & set #scaleChartsBy 0.2 & set #legendSize 0.3 & set #alignCharts AlignLeft & set #hgap (-0.2) & set #vgap (-0.1)) (defaultGlyphStyle & set #size 0.01 & over #color (rgb (palette 0)) & set (#color % opac') 0.3 & set (#borderColor % opac') 0.3 & set #glyphShape (gpalette 0)) defaultHudOptions (defaultGlyphStyle & set #size 0.06 & over #color (rgb (palette 0)) & set #glyphShape (gpalette 0) & set (#color % opac') 0.3 & set (#borderColor % opac') 1) (defaultHudOptions & set (#axes % each % #item % #ticks % #textTick %? #style % #size) 0.07 & over #axes (drop 1) & set (#axes % each % #item % #ticks % #tick % numTicks') (Just 2)) 0.08 100 0.2 True
+
+-- | Parse charting options.
+parsePerfChartOptions :: PerfChartOptions -> Parser PerfChartOptions
+parsePerfChartOptions def =
+  (\c fp trunAt small big hist avs -> PerfChartOptions c fp trunAt small big hist avs (view #averagesStyle def) (view #averagesPaletteStart def) (view #averagesLegend def) (view #smallStyle def) (view #smallHud def) (view #bigStyle def) (view #bigHud def) (view #titleSize def) (view #histGrain def) (view #bigWidth def) (view #excludeZeros def))
+    <$> switch (long "chart" <> short 'c' <> help "chart the result")
+    <*> option str (value (view #chartFilepath def) <> showDefault <> long "chartpath" <> metavar "FILE" <> help "chart file name")
+    <*> option auto (value (view #truncateAt def) <> showDefaultWith show <> long "truncateat" <> help "truncate chart data (multiple of median)")
+    <*> switch (long "small")
+    <*> switch (long "big")
+    <*> switch (long "histogram")
+    <*> switch (long "averages")
+
+perfCharts :: PerfChartOptions -> Maybe [Text] -> Map.Map Text [[Double]] -> ChartOptions
+perfCharts cfg labels m = bool (stackCO stackn AlignLeft NoAlign 0.1 cs) (head cs) (length cs == 1)
+  where
+    stackn = length cs & fromIntegral & sqrt @Double & ceiling
+    cs = uncurry (perfChart cfg) <$> ps'
+    ps = mconcat $ fmap (uncurry zip . bimap (\t -> fmap ((t <> ": ") <>) (fromMaybe (Text.pack . show @Int <$> [0 ..]) labels)) List.transpose) (Map.toList m)
+    ps' = filter ((> 0) . sum . snd) ps
+
+perfChart :: PerfChartOptions -> Text -> [Double] -> ChartOptions
+perfChart cfg t xs = finalChart
+  where
+    xsSmall = xs & xify & filter (_y >>> (< upperCutoff)) & filter (\x -> view #excludeZeros cfg && (_y x > 0))
+    xsBig = xs & xify & filter (_y >>> (>= upperCutoff))
+    med = median xs
+    best = tenth xs
+    av = Perf.average xs
+    upperCutoff = view #truncateAt cfg * med
+
+    labels =
+      [ "average: " <> comma (Just 3) av,
+        "median: " <> comma (Just 3) med,
+        "best: " <> comma (Just 3) best
+      ]
+    (Rect _ _ y' w') = fromMaybe one $ space1 xsSmall
+    (Range x' z') = Range zero (fromIntegral $ length xs)
+    rectx = BlankChart defaultStyle [Rect x' z' y' w']
+    averagesCT = named "averages" $ zipWith (\x i -> GlyphChart (view #averagesStyle cfg & set #color (palette i) & set #borderColor (palette i) & set #glyphShape (gpalette i)) [Point zero x]) [av, med, best] [(view #averagesPaletteStart cfg) ..]
+
+    (smallDot, smallHist) = dotHistChart (view #histGrain cfg) (view #smallStyle cfg) (mempty @ChartOptions & set #chartTree (averagesCT <> named "xrange" [rectx]) & set #hudOptions (view #smallHud cfg)) xsSmall
+
+    minb = minimum (_y <$> xsBig)
+    bigrange = Rect x' z' (bool minb zero (length xsBig == 1)) minb
+    (bigDot, bigHist) = dotHistChart (view #histGrain cfg) (view #bigStyle cfg) (mempty @ChartOptions & set #hudOptions (view #bigHud cfg) & set #chartTree (named "xrange" [BlankChart defaultStyle [bigrange]])) xsBig
+
+    (Rect bdX bdW _ _) = fromMaybe one $ view styleBox' (asChartTree bigDot)
+    bdr = Just $ Rect bdX bdW (-(view #bigWidth cfg)) (view #bigWidth cfg)
+    (Rect bdhX bdhW _ _) = fromMaybe one $ view styleBox' (asChartTree bigHist)
+    bhr = Just $ Rect bdhX bdhW (-(view #bigWidth cfg)) (view #bigWidth cfg)
+
+    finalChart =
+      mempty @ChartOptions
+        & set
+          #chartTree
+          ( stack
+              2
+              NoAlign
+              NoAlign
+              0
+              ( bool (asChartTree bigDot & set styleBox' bdr & pure) mempty (null xsBig)
+                  <> bool (asChartTree bigHist & set styleBox' bhr & pure) mempty (null xsBig)
+                  <> [ asChartTree smallDot,
+                       asChartTree smallHist
+                     ]
+              )
+          )
+        & set
+          (#hudOptions % #legends)
+          [Priority 10 (view #averagesLegend cfg & set #legendCharts (zipWith (\t' c -> (t', [c])) labels (toListOf chart' averagesCT)))]
+        & set
+          (#hudOptions % #titles)
+          [Priority 5 (defaultTitleOptions t & set (#style % #size) (view #titleSize cfg))]
+
+dotHistChart :: Int -> Style -> ChartOptions -> [Point Double] -> (ChartOptions, ChartOptions)
+dotHistChart grain gstyle co xs = (dotCO, histCO)
+  where
+    dotCT = named "dot" [GlyphChart gstyle xs]
+    ys = fmap _y xs
+    (Range l u) = fromMaybe one (space1 ys)
+    r' = bool (Range l u) (Range 0 l) (l == u)
+    r = computeRangeTick r' (fromMaybe defaultTick (co & preview (#hudOptions % #axes % ix 1 % #item % #ticks % #tick)))
+    (y, w) = let (Range y' w') = r in bool (y', w') (y' - 0.5, y' + 0.5) (y' == w')
+
+    histCO = hhistChart r grain ys & set (#markupOptions % #chartAspect) (CanvasAspect 0.3) & over #chartTree (<> unnamed [BlankChart defaultStyle [Rect 0 0 y w]])
+    dotCO = co & over #chartTree (dotCT <>)
+
+compareCharts :: [(PerfChartOptions, Text, [Double])] -> ChartOptions
+compareCharts xs = finalChart
+  where
+    xs' = xs & fmap (\(_, _, x) -> x)
+    cfg' = xs & fmap (\(x, _, _) -> x)
+    t' = xs & fmap (\(_, x, _) -> x)
+    cfg = head cfg'
+    xsSmall = xs' & fmap (xify >>> filter (_y >>> (< upperCutoff)) >>> filter (\x -> view #excludeZeros cfg && (_y x > 0)))
+    xsBig = xs' & fmap (xify >>> filter (_y >>> (>= upperCutoff)))
+    med = median <$> xs'
+    upperCutoff = view #truncateAt cfg * maximum med
+
+    (Rect _ _ y' w') = fromMaybe one $ space1 $ mconcat xsSmall
+    (Range x' z') = Range zero (fromIntegral $ maximum (length <$> xs'))
+    rectx = BlankChart defaultStyle [Rect x' z' y' w']
+
+    (smallDot, smallHist) = dotHistCharts (view #histGrain cfg) (mempty @ChartOptions & set #hudOptions (view #smallHud cfg) & set #chartTree (unnamed [rectx])) (zip (view #smallStyle <$> cfg') xsSmall)
+
+    minb = minimum (_y <$> mconcat xsBig)
+    bigrange = Rect x' z' (bool minb zero (length (mconcat xsBig) == 1)) minb
+    (bigDot, bigHist) = dotHistCharts (view #histGrain cfg) (mempty @ChartOptions & set #hudOptions (view #bigHud cfg) & set #chartTree (named "xrange" [BlankChart defaultStyle [bigrange]])) (zip (view #bigStyle <$> cfg') xsBig)
+
+    (Rect bdX bdW _ _) = fromMaybe one $ view styleBox' (asChartTree bigDot)
+    bdr = Just $ Rect bdX bdW (-(view #bigWidth cfg)) (view #bigWidth cfg)
+    (Rect bdhX bdhW _ _) = fromMaybe one $ view styleBox' (asChartTree bigHist)
+    bhr = Just $ Rect bdhX bdhW (-(view #bigWidth cfg)) (view #bigWidth cfg)
+
+    finalChart =
+      mempty @ChartOptions
+        & set
+          #chartTree
+          ( stack
+              2
+              NoAlign
+              NoAlign
+              0
+              ( bool (asChartTree bigDot & set styleBox' bdr & pure) mempty (null xsBig)
+                  <> bool (asChartTree bigHist & set styleBox' bhr & pure) mempty (null xsBig)
+                  <> [ asChartTree smallDot,
+                       asChartTree smallHist
+                     ]
+              )
+          )
+        & set
+          (#hudOptions % #legends)
+          [Priority 10 (view #averagesLegend cfg & set #legendCharts (zipWith (\t'' c -> (t'', [c])) t' (toListOf (#chartTree % chart') smallDot)))]
+
+dotHistCharts :: Int -> ChartOptions -> [(Style, [Point Double])] -> (ChartOptions, ChartOptions)
+dotHistCharts grain co xs = (dotCO, histCO)
+  where
+    dotCTs = named "dot" (uncurry GlyphChart <$> xs)
+    ys = fmap _y . snd <$> xs
+    (Range l u) = fromMaybe one (space1 (mconcat ys))
+    r' = bool (Range l u) (Range 0 l) (l == u)
+    r = computeRangeTick r' (fromMaybe defaultTick (co & preview (#hudOptions % #axes % ix 1 % #item % #ticks % #tick)))
+    (y, w) = let (Range y' w') = r in bool (y', w') (y' - 0.5, y' + 0.5) (y' == w')
+
+    histCO = hhistCharts r grain (zip (fst <$> xs) ys) & set (#markupOptions % #chartAspect) (CanvasAspect 0.3) & over #chartTree (<> unnamed [BlankChart defaultStyle [Rect 0 0 y w]])
+    dotCO = co & over #chartTree (dotCTs <>)
diff --git a/src/Perf/Measure.hs b/src/Perf/Measure.hs
--- a/src/Perf/Measure.hs
+++ b/src/Perf/Measure.hs
@@ -12,6 +12,7 @@
 
 import Data.Text (Text)
 import Options.Applicative
+import Options.Applicative.Help.Pretty qualified as OA
 import Perf.Count
 import Perf.Space
 import Perf.Stats
@@ -21,12 +22,13 @@
 import Prelude hiding (cycle)
 
 -- | Command-line measurement options.
-data MeasureType = MeasureTime | MeasureSpace | MeasureSpaceTime | MeasureAllocation | MeasureCount deriving (Eq, Show)
+data MeasureType = MeasureTime | MeasureNTime | MeasureSpace | MeasureSpaceTime | MeasureAllocation | MeasureCount deriving (Eq, Show)
 
 -- | Parse command-line 'MeasureType' options.
 parseMeasure :: Parser MeasureType
 parseMeasure =
-  flag' MeasureTime (long "time" <> help "measure time performance")
+  flag' MeasureTime (long "time" <> style (OA.annotate OA.bold) <> help "measure time performance")
+    <|> flag' MeasureNTime (long "ntime" <> help "measure n*time performance")
     <|> flag' MeasureSpace (long "space" <> help "measure space performance")
     <|> flag' MeasureSpaceTime (long "spacetime" <> help "measure both space and time performance")
     <|> flag' MeasureAllocation (long "allocation" <> help "measure bytes allocated")
@@ -38,26 +40,29 @@
 measureDs mt c n =
   case mt of
     MeasureTime -> fmap ((: []) . fromIntegral) <$> timesWith c n
+    MeasureNTime -> pure . pure . fromIntegral <$> timesNWith c n
     MeasureSpace -> toMeasureN n (ssToList <$> space False)
     MeasureSpaceTime -> toMeasureN n ((\x y -> ssToList x <> [fromIntegral y]) <$> space False <*> stepTime)
     MeasureAllocation -> fmap ((: []) . fromIntegral) <$> toMeasureN n (allocation False)
     MeasureCount -> (: []) . fmap fromIntegral <$> toMeasureN n count
 
--- | unification of the different measurements to being a list of doubles.
+-- | unification of measurement labels
 measureLabels :: MeasureType -> [Text]
 measureLabels mt =
   case mt of
     MeasureTime -> ["time"]
+    MeasureNTime -> ["ntime"]
     MeasureSpace -> spaceLabels
     MeasureSpaceTime -> spaceLabels <> ["time"]
     MeasureAllocation -> ["allocation"]
     MeasureCount -> ["count"]
 
 -- | How to fold the list of performance measures.
-measureFinalStat :: MeasureType -> [Double] -> Double
-measureFinalStat mt =
+measureFinalStat :: MeasureType -> Int -> [Double] -> Double
+measureFinalStat mt n =
   case mt of
     MeasureTime -> average
+    MeasureNTime -> (/ fromIntegral n) . sum
     MeasureSpace -> average
     MeasureSpaceTime -> average
     MeasureAllocation -> average
diff --git a/src/Perf/Report.hs b/src/Perf/Report.hs
--- a/src/Perf/Report.hs
+++ b/src/Perf/Report.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 -- | Reporting on performance, potentially checking versus a canned results.
@@ -12,27 +13,31 @@
     ReportOptions (..),
     defaultReportOptions,
     parseReportOptions,
-    infoReportOptions,
+    PerfDumpOptions (..),
+    defaultPerfDumpOptions,
+    parsePerfDumpOptions,
+    fromDump,
     report,
     reportMain,
-    reportMainWith,
     writeResult,
     readResult,
     CompareResult (..),
     compareNote,
-    reportOrg2D,
+    report2D,
     Golden (..),
     defaultGolden,
     parseGolden,
     replaceDefaultFilePath,
+    parseClock,
+    reportToConsole,
   )
 where
 
+import Chart
 import Control.Exception
 import Control.Monad
 import Data.Bool
 import Data.Foldable
-import Data.FormatN hiding (format)
 import Data.List (intercalate)
 import Data.List qualified as List
 import Data.Map.Merge.Strict
@@ -41,13 +46,23 @@
 import Data.Text qualified as Text
 import Data.Text.IO qualified as Text
 import GHC.Generics
-import Options.Applicative
+import Optics.Core
+import Options.Applicative as OA
+import Options.Applicative.Help.Pretty
+import Perf.Algos
+import Perf.BigO
+import Perf.Chart
 import Perf.Measure
 import Perf.Stats
 import Perf.Time (defaultClock)
 import Perf.Types
+import Prettyprinter.Render.Text qualified as PP
 import System.Clock
 import System.Exit
+import System.Mem
+import Test.Tasty
+import Test.Tasty.Bench
+import Text.PrettyPrint.Boxes qualified as B
 import Text.Printf hiding (parseFormat)
 import Text.Read
 
@@ -60,97 +75,111 @@
 -- | Command-line parser for 'Header'
 parseHeader :: Parser Header
 parseHeader =
-  flag' Header (long "header" <> help "include headers")
-    <|> flag' NoHeader (long "noheader" <> help "dont include headers")
+  flag' Header (long "header" <> help "include headers in reporting")
+    <|> flag' NoHeader (long "noheader" <> help "dont include headers in reporting")
     <|> pure Header
 
 -- | Options for production of a performance report.
 data ReportOptions = ReportOptions
   { -- | Number of times to run a benchmark.
     reportN :: Int,
+    reportLength :: Int,
     reportClock :: Clock,
     reportStatDType :: StatDType,
     reportMeasureType :: MeasureType,
     reportGolden :: Golden,
     reportHeader :: Header,
-    reportCompare :: CompareLevels
+    reportCompare :: CompareLevels,
+    reportChart :: PerfChartOptions,
+    reportDump :: PerfDumpOptions,
+    reportGC :: Bool,
+    reportOrder :: OrderOptions,
+    reportTasty :: Bool
   }
   deriving (Eq, Show, Generic)
 
--- | Default options
---
--- >>> defaultReportOptions
--- ReportOptions {reportN = 1000, reportClock = MonotonicRaw, reportStatDType = StatAverage, reportMeasureType = MeasureTime, reportGolden = Golden {golden = "other/bench.perf", check = True, record = False}, reportHeader = Header, reportCompare = CompareLevels {errorLevel = 0.2, warningLevel = 5.0e-2, improvedLevel = 5.0e-2}}
+-- | Default reporting options
 defaultReportOptions :: ReportOptions
 defaultReportOptions =
   ReportOptions
     1000
+    1000
     defaultClock
     StatAverage
     MeasureTime
     defaultGolden
     Header
     defaultCompareLevels
+    defaultPerfChartOptions
+    defaultPerfDumpOptions
+    False
+    defaultOrderOptions
+    False
 
 -- | Command-line parser for 'ReportOptions'
-parseReportOptions :: Parser ReportOptions
-parseReportOptions =
+parseReportOptions :: ReportOptions -> Parser ReportOptions
+parseReportOptions def =
   ReportOptions
-    <$> option auto (value 1000 <> long "runs" <> short 'n' <> help "number of runs to perform")
+    <$> option auto (value (view #reportN def) <> showDefaultWith show <> long "runs" <> short 'n' <> metavar "INT" <> help "number of runs to perform")
+    <*> option auto (value (view #reportLength def) <> long "length" <> showDefaultWith show <> short 'l' <> metavar "INT" <> help "length-like variable eg, used to alter list length and compute order")
     <*> parseClock
     <*> parseStatD
     <*> parseMeasure
     <*> parseGolden
     <*> parseHeader
     <*> parseCompareLevels defaultCompareLevels
+    <*> parsePerfChartOptions defaultPerfChartOptions
+    <*> parsePerfDumpOptions defaultPerfDumpOptions
+    <*> switch (long "gc" <> help "run the GC prior to measurement")
+    <*> parseOrderOptions defaultOrderOptions
+    <*> switch (long "tasty" <> help "run tasty-bench")
 
 -- | Parse command-line 'Clock' options.
 parseClock :: Parser Clock
 parseClock =
-  flag' Monotonic (long "Monotonic")
-    <|> flag' Realtime (long "Realtime")
-    <|> flag' ProcessCPUTime (long "ProcessCPUTime")
-    <|> flag' ThreadCPUTime (long "ThreadCPUTime")
+  flag' Monotonic (long "Monotonic" <> OA.style (annotate bold) <> help "use Monotonic clock")
+    <|> flag' Realtime (long "Realtime" <> help "use Realtime clock")
+    <|> flag' ProcessCPUTime (long "ProcessCPUTime" <> help "use ProcessCPUTime clock")
+    <|> flag' ThreadCPUTime (long "ThreadCPUTime" <> help "use ThreadCPUTime clock")
 #ifdef mingw32_HOST_OS
     <|> pure ThreadCPUTime
 #else
-    <|> flag' MonotonicRaw (long "MonotonicRaw")
+    <|> flag' MonotonicRaw (long "MonotonicRaw" <> help "use MonotonicRaw clock")
     <|> pure MonotonicRaw
 #endif
 
--- | Default command-line parser.
-infoReportOptions :: ParserInfo ReportOptions
-infoReportOptions =
-  info
-    (parseReportOptions <**> helper)
-    (fullDesc <> progDesc "perf benchmarking" <> header "reporting options")
+data PerfDumpOptions = PerfDumpOptions {dumpFilepath :: FilePath, doDump :: Bool} deriving (Eq, Show, Generic)
 
--- | Run and report a benchmark to the console. For example,
---
--- @reportMain "foo" (fap "sum" sum [1..1000])@ would:
---
--- - run a benchmark for summing the numbers 1 to a thousand.
---
--- - look for saved performance data in other/foo-1000-MeasureTime-StatAverage.perf
---
--- - report on performance in isolation or versus the canned data file if it exists.
---
--- - exit with failure if the performace had degraded.
-reportMain :: Name -> PerfT IO [[Double]] a -> IO ()
-reportMain name t = do
-  o <- execParser infoReportOptions
-  reportMainWith o name t
+defaultPerfDumpOptions :: PerfDumpOptions
+defaultPerfDumpOptions = PerfDumpOptions "other/perf.map" False
 
--- | Run and report a benchmark to the console with the supplied options.
-reportMainWith :: ReportOptions -> Name -> PerfT IO [[Double]] a -> IO ()
-reportMainWith o name t = do
+-- | Parse charting options.
+parsePerfDumpOptions :: PerfDumpOptions -> Parser PerfDumpOptions
+parsePerfDumpOptions def =
+  PerfDumpOptions
+    <$> option str (value (view #dumpFilepath def) <> showDefaultWith show <> long "dumppath" <> metavar "FILE" <> help "dump file name")
+    <*> switch (long "dump" <> help "dump raw performance data as a Map Text [[Double]]")
+
+fromDump :: PerfDumpOptions -> IO (Map.Map Text [[Double]])
+fromDump cfg = read <$> readFile (view #dumpFilepath cfg)
+
+-- | Run and report a benchmark with the specified reporting options.
+reportMain :: Example -> ReportOptions -> Name -> (Int -> PerfT IO [[Double]] a) -> IO a
+reportMain ex o name t = do
   let !n = reportN o
+  let l = reportLength o
   let s = reportStatDType o
   let c = reportClock o
   let mt = reportMeasureType o
   let o' = replaceDefaultFilePath (intercalate "-" [name, show n, show mt, show s]) o
-  m <- execPerfT (measureDs mt c n) t
+  when (reportGC o) performGC
+  (a, m) <- runPerfT (measureDs mt c n) (t l)
   report o' (statify s m)
+  (\cfg -> when (view #doChart cfg) (writeChartOptions (view #chartFilepath cfg) (perfCharts cfg (Just (measureLabels mt)) m))) (reportChart o)
+  (\cfg -> when (view #doDump cfg) (writeFile (view #dumpFilepath cfg) (show m))) (reportDump o)
+  when (view (#reportOrder % #doOrder) o) (reportBigO o t)
+  when (view #reportTasty o) (reportTasty' ex o)
+  pure a
 
 -- | Levels of geometric difference in compared performance that triggers reporting.
 data CompareLevels = CompareLevels {errorLevel :: Double, warningLevel :: Double, improvedLevel :: Double} deriving (Eq, Show)
@@ -165,9 +194,9 @@
 parseCompareLevels :: CompareLevels -> Parser CompareLevels
 parseCompareLevels c =
   CompareLevels
-    <$> option auto (value (errorLevel c) <> long "error" <> help "error level")
-    <*> option auto (value (warningLevel c) <> long "warning" <> help "warning level")
-    <*> option auto (value (improvedLevel c) <> long "improved" <> help "improved level")
+    <$> option auto (value (errorLevel c) <> showDefaultWith show <> long "error" <> metavar "DOUBLE" <> help "report an error if performance degrades by more than this")
+    <*> option auto (value (warningLevel c) <> showDefaultWith show <> long "warning" <> metavar "DOUBLE" <> help "report a warning if performance degrades by more than this")
+    <*> option auto (value (improvedLevel c) <> showDefaultWith show <> long "improved" <> metavar "DOUBLE" <> help "report if performance improves by more than this")
 
 -- | Write results to file
 writeResult :: FilePath -> Map.Map [Text] Double -> IO ()
@@ -231,32 +260,32 @@
     <> Map.elems (Map.mapWithKey (\k a -> Text.pack . mconcat $ printf "%-16s" <$> (k <> [a])) m)
 
 -- | Format a result as a table.
-reportOrg2D :: Map.Map [Text] Text -> IO ()
-reportOrg2D m = do
-  let rs = List.nub ((List.!! 1) . fst <$> Map.toList m)
-  let cs = List.nub ((List.!! 0) . fst <$> Map.toList m)
-  Text.putStrLn ("||" <> Text.intercalate "|" rs <> "|")
-  mapM_
-    ( \c ->
-        Text.putStrLn
-          ( "|"
-              <> c
-              <> "|"
-              <> Text.intercalate "|" ((\r -> m Map.! [c, r]) <$> rs)
-              <> "|"
-          )
-    )
-    cs
+report2D :: Map.Map [Text] Double -> IO ()
+report2D m = putStrLn $ B.render $ B.hsep 1 B.left $ cs' : rs'
+  where
+    rs = List.nub ((List.!! 1) . fst <$> Map.toList m)
+    cs = List.nub ((List.!! 0) . fst <$> Map.toList m)
+    bx = B.text . Text.unpack
+    xs = (\c -> (\r -> m Map.! [c, r]) <$> rs) <$> cs
+    xs' = fmap (fmap (bx . expt (Just 3))) xs
+    cs' = B.vcat B.left (bx <$> ("algo" : cs))
+    rs' = B.vcat B.right <$> zipWith (:) (bx <$> rs) (List.transpose xs')
 
 reportToConsole :: [Text] -> IO ()
 reportToConsole xs = traverse_ Text.putStrLn xs
 
 -- | Golden file options.
-data Golden = Golden {golden :: FilePath, check :: Bool, record :: Bool} deriving (Generic, Eq, Show)
+data Golden = Golden {golden :: FilePath, check :: CheckGolden, record :: RecordGolden} deriving (Generic, Eq, Show)
 
--- | Default filepath is "other/bench.perf"
+-- | Whether to check against a golden file
+data CheckGolden = CheckGolden | NoCheckGolden deriving (Eq, Show, Generic)
+
+-- | Whether to overwrite a golden file
+data RecordGolden = RecordGolden | NoRecordGolden deriving (Eq, Show, Generic)
+
+-- | Default is Golden "other/bench.perf" CheckGolden NoRecordGolden
 defaultGolden :: Golden
-defaultGolden = Golden "other/bench.perf" True False
+defaultGolden = Golden "other/bench.perf" CheckGolden NoRecordGolden
 
 -- | Replace the golden file name stem if it's the default.
 replaceGoldenDefault :: FilePath -> Golden -> Golden
@@ -274,10 +303,10 @@
 parseGolden :: Parser Golden
 parseGolden =
   Golden
-    <$> option str (Options.Applicative.value (golden defaultGolden) <> long "golden" <> short 'g' <> help "golden file name")
+    <$> option str (value (golden defaultGolden) <> showDefaultWith show <> long "golden" <> short 'g' <> metavar "FILE" <> help "golden file name")
     -- True is the default for 'check'.
-    <*> flag True False (long "nocheck" <> help "do not check versus the golden file")
-    <*> switch (long "record" <> short 'r' <> help "record the result to the golden file")
+    <*> (bool NoCheckGolden CheckGolden <$> flag True False (long "nocheck" <> help "do not check versus the golden file"))
+    <*> (bool NoRecordGolden RecordGolden <$> switch (long "record" <> short 'r' <> help "record the result to the golden file"))
 
 reportConsoleNoCompare :: Header -> Map.Map [Text] Double -> IO ()
 reportConsoleNoCompare h m =
@@ -293,17 +322,17 @@
 report :: ReportOptions -> Map.Map [Text] [Double] -> IO ()
 report o m = do
   when
-    (record (reportGolden o))
+    ((== RecordGolden) $ record (reportGolden o))
     (writeResult (golden (reportGolden o)) m')
   case check (reportGolden o) of
-    False -> reportConsoleNoCompare (reportHeader o) m'
-    True -> do
+    NoCheckGolden -> reportConsoleNoCompare (reportHeader o) m'
+    CheckGolden -> do
       mOrig <- readResult (golden (reportGolden o))
       case mOrig of
         Left _ -> do
           reportConsoleNoCompare (reportHeader o) m'
           unless
-            (record (reportGolden o))
+            ((RecordGolden ==) $ record (reportGolden o))
             (putStrLn "No golden file found. To create one, run with '-r'")
         Right orig -> do
           let n = compareNote (reportCompare o) orig m'
@@ -311,3 +340,22 @@
           when (hasDegraded n) (exitWith $ ExitFailure 1)
   where
     m' = Map.fromList $ mconcat $ (\(ks, xss) -> zipWith (\x l -> (ks <> [l], x)) xss (measureLabels (reportMeasureType o))) <$> Map.toList m
+
+reportBigO :: ReportOptions -> (Int -> PerfT IO [[Double]] a) -> IO ()
+reportBigO o p = do
+  m <- mapM (execPerfT (measureDs (view #reportMeasureType o) (view #reportClock o) (view #reportN o)) . p) ns
+  putStrLn mempty
+  reportToConsole $ PP.renderStrict . layoutPretty defaultLayoutOptions <$> os'' m
+  pure ()
+  where
+    l = view #reportLength o
+    ns = makeNs l (view (#reportOrder % #orderDivisor) o) (view (#reportOrder % #orderLow) o)
+    ms m' = fmap (fmap (statD (view #reportStatDType o)) . List.transpose) <$> m'
+    os m' = fmap (fmap (pretty . fromOrder . fst . estO (fromIntegral <$> ns)) . List.transpose) (Map.unionsWith (<>) (fmap (fmap (: [])) (ms m')))
+    os' m' = mconcat $ (\(ks, xss) -> zipWith (\x l' -> ([ks] <> [l'], x)) xss (measureLabels (reportMeasureType o))) <$> Map.toList (os m')
+    os'' m' = (\(k, v) -> (pretty . Text.intercalate ":") k <> " " <> v) <$> os' m'
+
+reportTasty' :: Example -> ReportOptions -> IO ()
+reportTasty' ex o = do
+  t <- measureCpuTime (mkTimeout 1000000) (RelStDev 0.05) (tastyExample (examplePattern ex (view #reportLength o)))
+  Text.putStrLn $ "tasty:time: " <> decimal (Just 3) (t * 1e9)
diff --git a/src/Perf/Space.hs b/src/Perf/Space.hs
--- a/src/Perf/Space.hs
+++ b/src/Perf/Space.hs
@@ -20,7 +20,7 @@
 import Prelude hiding (cycle)
 
 -- | GHC allocation statistics.
-data SpaceStats = SpaceStats {allocatedBytes :: Word64, gcollects :: Word32, maxLiveBytes :: Word64, gcLiveBytes :: Word64, maxMem :: Word64} deriving (Read, Show, Eq)
+data SpaceStats = SpaceStats {allocated :: Word64, copied :: Word64, maxmem :: Word64, minorgcs :: Word32, majorgcs :: Word32} deriving (Read, Show, Eq)
 
 -- | Convert 'SpaceStats' to a list of numbers.
 ssToList :: (Num a) => SpaceStats -> [a]
@@ -47,11 +47,11 @@
 addSpace (SpaceStats x1 x2 x3 x4 x5) (SpaceStats x1' x2' x3' x4' x5') = SpaceStats (x1' + x1) (x2' + x2) (x3' + x3) (x4' + x4) (x5' + x5)
 
 getSpace :: RTSStats -> SpaceStats
-getSpace s = SpaceStats (allocated_bytes s) (gcs s) (max_live_bytes s) (gcdetails_live_bytes (gc s)) (max_mem_in_use_bytes s)
+getSpace s = SpaceStats (allocated_bytes s) (copied_bytes s) (max_mem_in_use_bytes s) (gcs s) (major_gcs s)
 
 -- | Labels for 'SpaceStats'.
 spaceLabels :: [Text]
-spaceLabels = ["allocated", "gcollects", "maxLiveBytes", "gcLiveBytes", "MaxMem"]
+spaceLabels = ["allocated", "copied", "maxmem", "minorgcs", "majorgcs"]
 
 -- | A allocation 'StepMeasure' with a flag to determine if 'performGC' should run prior to the measurement.
 space :: Bool -> StepMeasure IO SpaceStats
diff --git a/src/Perf/Stats.hs b/src/Perf/Stats.hs
--- a/src/Perf/Stats.hs
+++ b/src/Perf/Stats.hs
@@ -24,6 +24,7 @@
 import Data.Text (Text, pack)
 import NumHask.Space (quantile)
 import Options.Applicative
+import Options.Applicative.Help.Pretty
 
 -- | Compute the median
 median :: [Double] -> Double
@@ -59,10 +60,10 @@
 -- | Parse command-line 'StatDType' options.
 parseStatD :: Parser StatDType
 parseStatD =
-  flag' StatBest (long "best" <> help "report upper decile")
+  flag' StatBest (long "best" <> style (annotate bold) <> help "report upper decile")
     <|> flag' StatMedian (long "median" <> help "report median")
     <|> flag' StatAverage (long "average" <> help "report average")
-    <|> pure StatAverage
+    <|> pure StatBest
 
 -- | Add a statistic to a State Map
 addStat :: (Ord k, Monad m) => k -> s -> StateT (Map.Map k s) m ()
diff --git a/src/Perf/Time.hs b/src/Perf/Time.hs
--- a/src/Perf/Time.hs
+++ b/src/Perf/Time.hs
@@ -17,11 +17,14 @@
     tickForce,
     tickForceArgs,
     tickIO,
+    tickIOWith,
     ticks,
     ticksIO,
     time,
     times,
     timesWith,
+    timesN,
+    timesNWith,
     stepTime,
   )
 where
@@ -191,15 +194,33 @@
 
 -- | tick as a 'Measure'
 time :: Measure IO Nanos
-time = Measure tick tickIO
+time = Measure tick
 {-# INLINEABLE time #-}
 
 -- | tick as a multi-Measure
 times :: Int -> Measure IO [Nanos]
-times n = Measure (ticks n) (ticksIO n)
+times n = Measure (ticks n)
 {-# INLINEABLE times #-}
 
 -- | tickWith as a multi-Measure
 timesWith :: Clock -> Int -> Measure IO [Nanos]
-timesWith c n = Measure (multi (tickWith c) n) (multiM (tickIOWith c) n)
+timesWith c n = repeated n (Measure (tickWith c))
 {-# INLINEABLE timesWith #-}
+
+-- | tickWith for n repeated applications
+timesN :: Int -> Measure IO Nanos
+timesN n = Measure (tickNWith defaultClock n)
+{-# INLINEABLE timesN #-}
+
+-- | tickWith for n repeated applications
+timesNWith :: Clock -> Int -> Measure IO Nanos
+timesNWith c n = Measure (tickNWith c n)
+{-# INLINEABLE timesNWith #-}
+
+tickNWith :: Clock -> Int -> (a -> b) -> a -> IO (Nanos, b)
+tickNWith c n !f !a = do
+  !t <- nanosWith c
+  !a' <- multiN id f a n
+  !t' <- nanosWith c
+  pure (floor @Double (fromIntegral (t' - t) / fromIntegral n), a')
+{-# INLINEABLE tickNWith #-}
diff --git a/src/Perf/Types.hs b/src/Perf/Types.hs
--- a/src/Perf/Types.hs
+++ b/src/Perf/Types.hs
@@ -13,6 +13,7 @@
     stepM,
     multi,
     multiM,
+    multiN,
 
     -- * function application
     fap,
@@ -43,37 +44,35 @@
 import Data.Functor.Identity
 import Data.Map.Strict qualified as Map
 import Data.Text (Text)
+import GHC.Exts
+import GHC.IO hiding (liftIO)
 import Prelude
 
 -- | Abstraction of a performance measurement within a monadic context.
 --
 -- - measure applies a function to a value, returning a tuple of the performance measure, and the computation result.
 -- - measureM evaluates a monadic value and returns a performance-result tuple.
-data Measure m t = Measure
-  { measure :: forall a b. (a -> b) -> a -> m (t, b),
-    measureM :: forall a. m a -> m (t, a)
+newtype Measure m t = Measure
+  { measure :: forall a b. (a -> b) -> a -> m (t, b)
   }
 
 instance (Functor m) => Functor (Measure m) where
-  fmap f (Measure m n) =
+  fmap f (Measure m) =
     Measure
       (\f' a' -> fmap (first f) (m f' a'))
-      (fmap (first f) . n)
 
 -- | An inefficient application that runs the inner action twice.
 instance (Applicative m) => Applicative (Measure m) where
-  pure t = Measure (\f a -> pure (t, f a)) (\a -> (t,) <$> a)
-  (Measure mf nf) <*> (Measure mt nt) =
+  pure t = Measure (\f a -> pure (t, f a))
+  (Measure mf) <*> (Measure mt) =
     Measure
       (\f a -> (\(nf', fa') (t', _) -> (nf' t', fa')) <$> mf f a <*> mt f a)
-      (\a -> (\(nf', a') (t', _) -> (nf' t', a')) <$> nf a <*> nt a)
 
 -- | Convert a Measure into a multi measure.
 repeated :: (Applicative m) => Int -> Measure m t -> Measure m [t]
-repeated n (Measure p m) =
+repeated n (Measure p) =
   Measure
     (\f a -> fmap (\xs -> (fmap fst xs, snd (head xs))) (replicateM n (p f a)))
-    (fmap (\xs -> (fmap fst xs, snd (head xs))) . replicateM n . m)
 {-# INLINEABLE repeated #-}
 
 -- | Abstraction of a performance measurement with a pre and a post step wrapping the computation.
@@ -89,12 +88,12 @@
 
 -- | Convert a StepMeasure into a Measure
 toMeasure :: (Monad m) => StepMeasure m t -> Measure m t
-toMeasure (StepMeasure pre' post') = Measure (step pre' post') (stepM pre' post')
+toMeasure (StepMeasure pre' post') = Measure (step pre' post')
 {-# INLINEABLE toMeasure #-}
 
 -- | Convert a StepMeasure into a Measure running the computation multiple times.
 toMeasureN :: (Monad m) => Int -> StepMeasure m t -> Measure m [t]
-toMeasureN n (StepMeasure pre' post') = Measure (multi (step pre' post') n) (multiM (stepM pre' post') n)
+toMeasureN n (StepMeasure pre' post') = Measure (multi (step pre' post') n)
 {-# INLINEABLE toMeasureN #-}
 
 -- | A single step measurement.
@@ -115,18 +114,33 @@
   pure (t, ma)
 {-# INLINEABLE stepM #-}
 
--- | Multiple measurement
+multi1 :: (Monad m) => ((a -> b) -> a -> m (t, b)) -> Int -> (a -> b) -> a -> m [(t, b)]
+multi1 action n !f !a = sequence $ replicate n $! action f a
+{-# INLINEABLE multi1 #-}
+
+-- | Return one result but multiple measurements.
 multi :: (Monad m) => ((a -> b) -> a -> m (t, b)) -> Int -> (a -> b) -> a -> m ([t], b)
-multi action n !f !a =
-  fmap (\xs -> (fmap fst xs, snd (head xs))) (replicateM n (action f a))
+multi action n !f !a = do
+  xs <- multi1 action n f a
+  pure (fmap fst xs, snd (head xs))
 {-# INLINEABLE multi #-}
 
 -- | Multiple measurements
 multiM :: (Monad m) => (m a -> m (t, a)) -> Int -> m a -> m ([t], a)
 multiM action n a =
-  fmap (\xs -> (fmap fst xs, snd (head xs))) (replicateM n (action a))
+  fmap (\xs -> (fmap fst xs, head $! fmap snd xs)) (replicateM n (action a))
 {-# INLINEABLE multiM #-}
 
+multiN :: (b -> t) -> (a -> b) -> a -> Int -> IO t
+multiN frc = multiNLoop SPEC
+  where
+    multiNLoop !_ f x n
+      | n == 1 = evaluate (frc (f x))
+      | otherwise = do
+          _ <- evaluate (frc (f x))
+          multiNLoop SPEC f x (n - 1)
+{-# INLINE multiN #-}
+
 -- | Performance measurement transformer storing a 'Measure' and a map of named results.
 newtype PerfT m t a = PerfT
   { measurePerf :: StateT (Measure m t, Map.Map Text t) m a
@@ -180,9 +194,9 @@
 fam label a =
   PerfT $ do
     m <- fst <$> get
-    (t, ma) <- lift $ measureM m a
+    (t, !ma) <- lift $ measure m (const a) ()
     modify $ second (Map.insertWith (<>) label t)
-    return ma
+    lift ma
 {-# INLINEABLE fam #-}
 
 -- | lift a pure, unnamed function application to PerfT
