Skip to content

Commit 7339784

Browse files
authored
Run benchmarks on a list of examples (#864)
- Cabal 3.0.0.0 - haskell-lsp-types 0.22.0.0
1 parent f26c4ab commit 7339784

File tree

4 files changed

+96
-57
lines changed

4 files changed

+96
-57
lines changed

bench/config.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ outputFolder: bench-results
1313
# Example project used to run the experiments
1414
# Can either be a Hackage package (name,version)
1515
# or a local project (path) with a valid `hie.yaml` file
16-
example:
17-
name: Cabal
16+
examples:
17+
# Medium-sized project without TH
18+
- name: Cabal
1819
version: 3.0.0.0
19-
# path: path/to/example
2020
module: Distribution/Simple.hs
21+
# Small-sized project with TH
22+
- name: haskell-lsp-types
23+
version: 0.22.0.0
24+
module: src/Language/Haskell/LSP/Types/Lens.hs
25+
# - path: path-to-example
26+
# module: path-to-module
2127

2228
# The set of experiments to execute
2329
experiments:

bench/hist/Main.hs

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
system with the following structure:
1111
1212
bench-results
13-
├── <git-reference> - one folder per version
14-
│   ├── <experiment>.benchmark-gcStats - RTS -s output
15-
│   ├── <experiment>.csv - stats for the experiment
16-
│   ├── <experiment>.svg - Graph of bytes over elapsed time
17-
│   ├── <experiment>.diff.svg - idem, including the previous version
18-
│   ├── <experiment>.log - ghcide-bench output
19-
│   ├── ghc.path - path to ghc used to build the binary
20-
│   ├── ghcide - binary for this version
21-
│   └── results.csv - results of all the experiments for the version
13+
├── <git-reference>
14+
│  ├── ghc.path - path to ghc used to build the binary
15+
│  ├── ghcide - binary for this version
16+
├─ <example>
17+
│ ├── results.csv - aggregated results for all the versions
18+
│ └── <git-reference>
19+
│   ├── <experiment>.benchmark-gcStats - RTS -s output
20+
│   ├── <experiment>.csv - stats for the experiment
21+
│   ├── <experiment>.svg - Graph of bytes over elapsed time
22+
│   ├── <experiment>.diff.svg - idem, including the previous version
23+
│   ├── <experiment>.log - ghcide-bench output
24+
│   └── results.csv - results of all the experiments for the example
2225
├── results.csv - aggregated results of all the experiments and versions
23-
── <experiment>.svg - graph of bytes over elapsed time, for all the included versions
26+
── <experiment>.svg - graph of bytes over elapsed time, for all the included versions
2427
2528
For diff graphs, the "previous version" is the preceding entry in the list of versions
2629
in the config file. A possible improvement is to obtain this info via `git rev-list`.
@@ -35,6 +38,7 @@
3538
> cabal bench --benchmark-options "bench-results/HEAD/results.csv bench-results/HEAD/edit.diff.svg"
3639
3740
-}
41+
{-# LANGUAGE ApplicativeDo #-}
3842
{-# LANGUAGE DeriveAnyClass #-}
3943
{-# LANGUAGE DerivingStrategies#-}
4044
{-# LANGUAGE TypeFamilies #-}
@@ -49,7 +53,7 @@ import qualified Data.Text as T
4953
import Data.Yaml ((.!=), (.:?), FromJSON (..), ToJSON (..), Value (..), decodeFileThrow)
5054
import Development.Shake
5155
import Development.Shake.Classes (Binary, Hashable, NFData)
52-
import Experiments.Types (exampleToOptions, Example(..))
56+
import Experiments.Types (getExampleName, exampleToOptions, Example(..))
5357
import GHC.Exts (IsList (..))
5458
import GHC.Generics (Generic)
5559
import qualified Graphics.Rendering.Chart.Backend.Diagrams as E
@@ -60,6 +64,7 @@ import System.Directory
6064
import System.FilePath
6165
import qualified Text.ParserCombinators.ReadP as P
6266
import Text.Read (Read (..), get, readMaybe, readP_to_Prec)
67+
import GHC.Stack (HasCallStack)
6368

6469
config :: FilePath
6570
config = "bench/config.yaml"
@@ -68,24 +73,20 @@ config = "bench/config.yaml"
6873
readConfigIO :: FilePath -> IO Config
6974
readConfigIO = decodeFileThrow
7075

76+
newtype GetExample = GetExample String deriving newtype (Binary, Eq, Hashable, NFData, Show)
77+
newtype GetExamples = GetExamples () deriving newtype (Binary, Eq, Hashable, NFData, Show)
7178
newtype GetSamples = GetSamples () deriving newtype (Binary, Eq, Hashable, NFData, Show)
72-
7379
newtype GetExperiments = GetExperiments () deriving newtype (Binary, Eq, Hashable, NFData, Show)
74-
7580
newtype GetVersions = GetVersions () deriving newtype (Binary, Eq, Hashable, NFData, Show)
76-
7781
newtype GetParent = GetParent Text deriving newtype (Binary, Eq, Hashable, NFData, Show)
78-
7982
newtype GetCommitId = GetCommitId String deriving newtype (Binary, Eq, Hashable, NFData, Show)
8083

84+
type instance RuleResult GetExample = Maybe Example
85+
type instance RuleResult GetExamples = [Example]
8186
type instance RuleResult GetSamples = Natural
82-
8387
type instance RuleResult GetExperiments = [Unescaped String]
84-
8588
type instance RuleResult GetVersions = [GitCommit]
86-
8789
type instance RuleResult GetParent = Text
88-
8990
type instance RuleResult GetCommitId = String
9091

9192
main :: IO ()
@@ -97,12 +98,16 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
9798
_ <- addOracle $ \GetSamples {} -> samples <$> readConfig config
9899
_ <- addOracle $ \GetExperiments {} -> experiments <$> readConfig config
99100
_ <- addOracle $ \GetVersions {} -> versions <$> readConfig config
101+
_ <- addOracle $ \GetExamples{} -> examples <$> readConfig config
100102
_ <- addOracle $ \(GetParent name) -> findPrev name . versions <$> readConfig config
103+
_ <- addOracle $ \(GetExample name) -> find (\e -> getExampleName e == name) . examples <$> readConfig config
101104

102105
let readVersions = askOracle $ GetVersions ()
103106
readExperiments = askOracle $ GetExperiments ()
107+
readExamples = askOracle $ GetExamples ()
104108
readSamples = askOracle $ GetSamples ()
105109
getParent = askOracle . GetParent
110+
getExample = askOracle . GetExample
106111

107112
configStatic <- liftIO $ readConfigIO config
108113
ghcideBenchPath <- ghcideBench <$> liftIO (readConfigIO config)
@@ -112,16 +117,16 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
112117
phony "all" $ do
113118
Config {..} <- readConfig config
114119

115-
forM_ versions $ \ver ->
116-
need [build </> T.unpack (humanName ver) </> "results.csv"]
117-
118120
need $
121+
[build </> getExampleName e </> "results.csv" | e <- examples ] ++
119122
[build </> "results.csv"]
120-
++ [ build </> escaped (escapeExperiment e) <.> "svg"
123+
++ [ build </> getExampleName ex </> escaped (escapeExperiment e) <.> "svg"
121124
| e <- experiments
125+
, ex <- examples
122126
]
123-
++ [ build </> T.unpack (humanName ver) </> escaped (escapeExperiment e) <.> mode <.> "svg"
127+
++ [ build </> getExampleName ex </> T.unpack (humanName ver) </> escaped (escapeExperiment e) <.> mode <.> "svg"
124128
| e <- experiments,
129+
ex <- examples,
125130
ver <- versions,
126131
mode <- ["", "diff"]
127132
]
@@ -136,7 +141,7 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
136141
Stdout commitid <- command [] "git" ["rev-list", "-n", "1", gitThing]
137142
writeFileChanged out $ init commitid
138143

139-
priority 10 $ [build -/- "HEAD/ghcide"
144+
priority 10 $ [ build -/- "HEAD/ghcide"
140145
, build -/- "HEAD/ghc.path"
141146
]
142147
&%> \[out, ghcpath] -> do
@@ -159,8 +164,7 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
159164
cmd_ [Cwd "bench-temp"] $ buildGhcide buildSystem (".." </> takeDirectory out)
160165
writeFile' ghcpath ghcLoc
161166

162-
priority 8000 $
163-
build -/- "*/results.csv" %> \out -> do
167+
build -/- "*/*/results.csv" %> \out -> do
164168
experiments <- readExperiments
165169

166170
let allResultFiles = [takeDirectory out </> escaped (escapeExperiment e) <.> "csv" | e <- experiments]
@@ -173,16 +177,17 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
173177
ghcideBenchResource <- newResource "ghcide-bench" 1
174178

175179
priority 0 $
176-
[ build -/- "*/*.csv",
177-
build -/- "*/*.benchmark-gcStats",
178-
build -/- "*/*.log"
180+
[ build -/- "*/*/*.csv",
181+
build -/- "*/*/*.benchmark-gcStats",
182+
build -/- "*/*/*.log"
179183
]
180184
&%> \[outcsv, _outGc, outLog] -> do
181-
let [_, _, exp] = splitDirectories outcsv
185+
let [_, exampleName, ver, exp] = splitDirectories outcsv
186+
example <- fromMaybe (error $ "Unknown example " <> exampleName) <$> getExample exampleName
182187
samples <- readSamples
183188
liftIO $ createDirectoryIfMissing True $ dropFileName outcsv
184-
let ghcide = dropFileName outcsv </> "ghcide"
185-
ghcpath = dropFileName outcsv </> "ghc.path"
189+
let ghcide = build </> ver </> "ghcide"
190+
ghcpath = build </> ver </> "ghc.path"
186191
need [ghcide, ghcpath]
187192
ghcPath <- readFile' ghcpath
188193
withResource ghcideBenchResource 1 $ do
@@ -203,53 +208,66 @@ main = shakeArgs shakeOptions {shakeChange = ChangeModtimeAndDigest} $ do
203208
"--select",
204209
unescaped (unescapeExperiment (Escaped $ dropExtension exp))
205210
] ++
206-
exampleToOptions (example configStatic) ++
211+
exampleToOptions example ++
207212
[ "--stack" | Stack == buildSystem]
208213
cmd_ Shell $ "mv *.benchmark-gcStats " <> dropFileName outcsv
209214

210215
build -/- "results.csv" %> \out -> do
211-
versions <- readVersions
212-
let allResultFiles =
213-
[build </> T.unpack (humanName v) </> "results.csv" | v <- versions]
216+
examples <- map getExampleName <$> readExamples
217+
let allResultFiles = [build </> e </> "results.csv" | e <- examples]
218+
219+
allResults <- traverse readFileLines allResultFiles
220+
221+
let header = head $ head allResults
222+
results = map tail allResults
223+
header' = "example, " <> header
224+
results' = zipWith (\e -> map (\l -> e <> ", " <> l)) examples results
225+
226+
writeFileChanged out $ unlines $ header' : concat results'
214227

215-
need [build </> T.unpack (humanName v) </> "ghcide" | v <- versions]
228+
build -/- "*/results.csv" %> \out -> do
229+
versions <- map (T.unpack . humanName) <$> readVersions
230+
let example = takeFileName $ takeDirectory out
231+
allResultFiles =
232+
[build </> example </> v </> "results.csv" | v <- versions]
216233

217234
allResults <- traverse readFileLines allResultFiles
218235

219236
let header = head $ head allResults
220237
results = map tail allResults
221238
header' = "version, " <> header
222-
results' = zipWith (\v -> map (\l -> T.unpack (humanName v) <> ", " <> l)) versions results
239+
results' = zipWith (\v -> map (\l -> v <> ", " <> l)) versions results
223240

224241
writeFileChanged out $ unlines $ header' : concat results'
225242

226243
priority 2 $
227-
build -/- "*/*.diff.svg" %> \out -> do
228-
let [b, ver, exp_] = splitDirectories out
244+
build -/- "*/*/*.diff.svg" %> \out -> do
245+
let [b, example, ver, exp_] = splitDirectories out
229246
exp = Escaped $ dropExtension $ dropExtension exp_
230247
prev <- getParent $ T.pack ver
231248

232-
runLog <- loadRunLog b exp ver
233-
runLogPrev <- loadRunLog b exp $ T.unpack prev
249+
runLog <- loadRunLog b example exp ver
250+
runLogPrev <- loadRunLog b example exp $ T.unpack prev
234251

235252
let diagram = Diagram Live [runLog, runLogPrev] title
236253
title = show (unescapeExperiment exp) <> " - live bytes over time compared"
237254
plotDiagram True diagram out
238255

239256
priority 1 $
240-
build -/- "*/*.svg" %> \out -> do
241-
let [b, ver, exp] = splitDirectories out
242-
runLog <- loadRunLog b (Escaped $ dropExtension exp) ver
257+
build -/- "*/*/*.svg" %> \out -> do
258+
let [b, example, ver, exp] = splitDirectories out
259+
runLog <- loadRunLog b example (Escaped $ dropExtension exp) ver
243260
let diagram = Diagram Live [runLog] title
244261
title = ver <> " live bytes over time"
245262
plotDiagram True diagram out
246263

247-
build -/- "*.svg" %> \out -> do
264+
build -/- "*/*.svg" %> \out -> do
248265
let exp = Escaped $ dropExtension $ takeFileName out
266+
example = takeFileName $ takeDirectory out
249267
versions <- readVersions
250268

251269
runLogs <- forM (filter include versions) $ \v -> do
252-
loadRunLog build exp $ T.unpack $ humanName v
270+
loadRunLog build example exp $ T.unpack $ humanName v
253271

254272
let diagram = Diagram Live runLogs title
255273
title = show (unescapeExperiment exp) <> " - live bytes over time"
@@ -282,7 +300,7 @@ findGhc Stack = do
282300

283301
data Config = Config
284302
{ experiments :: [Unescaped String],
285-
example :: Example,
303+
examples :: [Example],
286304
samples :: Natural,
287305
versions :: [GitCommit],
288306
-- | Path to the ghcide-bench binary for the experiments
@@ -401,14 +419,15 @@ data Diagram = Diagram
401419
-- | A file path containing the output of -S for a given run
402420
data RunLog = RunLog
403421
{ runVersion :: !String,
422+
_runExample :: !String,
404423
_runExperiment :: !String,
405424
runFrames :: ![Frame],
406425
runSuccess :: !Bool
407426
}
408427

409-
loadRunLog :: FilePath -> Escaped FilePath -> FilePath -> Action RunLog
410-
loadRunLog buildF exp ver = do
411-
let log_fp = buildF </> ver </> escaped exp <.> "benchmark-gcStats"
428+
loadRunLog :: HasCallStack => FilePath -> String -> Escaped FilePath -> FilePath -> Action RunLog
429+
loadRunLog buildF example exp ver = do
430+
let log_fp = buildF </> example </> ver </> escaped exp <.> "benchmark-gcStats"
412431
csv_fp = replaceExtension log_fp "csv"
413432
log <- readFileLines log_fp
414433
csv <- readFileLines csv_fp
@@ -422,7 +441,7 @@ loadRunLog buildF exp ver = do
422441
success = case map (T.split (== ',') . T.pack) csv of
423442
[_header, _name:s:_] | Just s <- readMaybe (T.unpack s) -> s
424443
_ -> error $ "Cannot parse: " <> csv_fp
425-
return $ RunLog ver (dropExtension $ escaped exp) frames success
444+
return $ RunLog ver example (dropExtension $ escaped exp) frames success
426445

427446
plotDiagram :: Bool -> Diagram -> FilePath -> Action ()
428447
plotDiagram includeFailed t@Diagram {traceMetric, runLogs} out = do

bench/lib/Experiments/Types.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ module Experiments.Types where
55
import Data.Aeson
66
import Data.Version
77
import Numeric.Natural
8+
import System.FilePath (isPathSeparator)
9+
import Development.Shake.Classes
10+
import GHC.Generics
811

912
data CabalStack = Cabal | Stack
1013
deriving (Eq, Show)
@@ -29,7 +32,17 @@ data Config = Config
2932
data Example
3033
= GetPackage {exampleName, exampleModule :: String, exampleVersion :: Version}
3134
| UsePackage {examplePath :: FilePath, exampleModule :: String}
32-
deriving (Eq, Show)
35+
deriving (Eq, Generic, Show)
36+
deriving anyclass (Binary, Hashable, NFData)
37+
38+
getExampleName :: Example -> String
39+
getExampleName UsePackage{examplePath} = map replaceSeparator examplePath
40+
where
41+
replaceSeparator x
42+
| isPathSeparator x = '_'
43+
| otherwise = x
44+
getExampleName GetPackage{exampleName, exampleVersion} =
45+
exampleName <> "-" <> showVersion exampleVersion
3346

3447
instance FromJSON Example where
3548
parseJSON = withObject "example" $ \x -> do

ghcide.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ executable ghcide-bench
399399
optparse-applicative,
400400
process,
401401
safe-exceptions,
402+
shake,
402403
text
403404
hs-source-dirs: bench/lib bench/exe
404405
include-dirs: include

0 commit comments

Comments
 (0)