Skip to content

Show build graph statistics in ghcide-bench #2343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions ghcide/bench/lib/Experiments.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ import Control.Exception.Safe (IOException, handleAny, try)
import Control.Monad.Extra
import Control.Monad.IO.Class
import Data.Aeson (Value (Null), toJSON)
import Data.Either (fromRight)
import Data.List
import Data.Maybe
import qualified Data.Text as T
import Data.Version
import Development.IDE.Plugin.Test
import Development.IDE.Test (getBuildEdgesCount,
getBuildKeysBuilt,
getBuildKeysChanged,
getBuildKeysVisited,
getStoredKeys)
import Development.IDE.Test.Diagnostic
import Development.Shake (CmdOption (Cwd, FileStdout),
cmd_)
Expand Down Expand Up @@ -323,6 +329,11 @@ runBenchmarksFun dir allBenchmarks = do
, "userTime"
, "delayedTime"
, "totalTime"
, "buildRulesBuilt"
, "buildRulesChanged"
, "buildRulesVisited"
, "buildRulesTotal"
, "buildEdges"
]
rows =
[ [ name,
Expand All @@ -332,7 +343,12 @@ runBenchmarksFun dir allBenchmarks = do
show runSetup',
show userWaits,
show delayedWork,
show runExperiment
show runExperiment,
show rulesBuilt,
show rulesChanged,
show rulesVisited,
show rulesTotal,
show edgesTotal
]
| (Bench {name, samples}, BenchRun {..}) <- results,
let runSetup' = if runSetup < 0.01 then 0 else runSetup
Expand All @@ -352,7 +368,12 @@ runBenchmarksFun dir allBenchmarks = do
showDuration runSetup',
showDuration userWaits,
showDuration delayedWork,
showDuration runExperiment
showDuration runExperiment,
show rulesBuilt,
show rulesChanged,
show rulesVisited,
show rulesTotal,
show edgesTotal
]
| (Bench {name, samples}, BenchRun {..}) <- results,
let runSetup' = if runSetup < 0.01 then 0 else runSetup
Expand Down Expand Up @@ -398,11 +419,16 @@ data BenchRun = BenchRun
runExperiment :: !Seconds,
userWaits :: !Seconds,
delayedWork :: !Seconds,
rulesBuilt :: !Int,
rulesChanged :: !Int,
rulesVisited :: !Int,
rulesTotal :: !Int,
edgesTotal :: !Int,
success :: !Bool
}

badRun :: BenchRun
badRun = BenchRun 0 0 0 0 0 False
badRun = BenchRun 0 0 0 0 0 0 0 0 0 0 False

waitForProgressStart :: Session ()
waitForProgressStart = void $ do
Expand Down Expand Up @@ -470,6 +496,12 @@ runBench runSess b = handleAny (\e -> print e >> return badRun)
let success = isJust result
(userWaits, delayedWork) = fromMaybe (0,0) result

rulesTotal <- length <$> getStoredKeys
rulesBuilt <- either (const 0) length <$> getBuildKeysBuilt
rulesChanged <- either (const 0) length <$> getBuildKeysChanged
rulesVisited <- either (const 0) length <$> getBuildKeysVisited
edgesTotal <- fromRight 0 <$> getBuildEdgesCount

return BenchRun {..}

data SetupResult = SetupResult {
Expand Down
3 changes: 3 additions & 0 deletions ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ executable ghcide-bench
extra,
filepath,
ghcide,
hls-plugin-api,
lens,
lsp-test,
lsp-types,
Expand All @@ -454,11 +455,13 @@ executable ghcide-bench
safe-exceptions,
hls-graph,
shake,
tasty-hunit,
text
hs-source-dirs: bench/lib bench/exe test/src
ghc-options: -threaded -Wall -Wno-name-shadowing -rtsopts
main-is: Main.hs
other-modules:
Development.IDE.Test
Development.IDE.Test.Diagnostic
Experiments
Experiments.Types
Expand Down
61 changes: 44 additions & 17 deletions ghcide/src/Development/IDE/Plugin/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,40 @@ module Development.IDE.Plugin.Test
, blockCommandId
) where

import Control.Concurrent (threadDelay)
import Control.Concurrent.Extra (readVar)
import Control.Concurrent (threadDelay)
import Control.Concurrent.Extra (readVar)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.STM
import Data.Aeson
import Data.Aeson.Types
import Data.Bifunctor
import Data.CaseInsensitive (CI, original)
import qualified Data.HashMap.Strict as HM
import Data.Maybe (isJust)
import Data.CaseInsensitive (CI, original)
import qualified Data.HashMap.Strict as HM
import Data.Maybe (isJust)
import Data.String
import Data.Text (Text, pack)
import Development.IDE.Core.OfInterest (getFilesOfInterest)
import Data.Text (Text, pack)
import Development.IDE.Core.OfInterest (getFilesOfInterest)
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Service
import Development.IDE.Core.Shake
import Development.IDE.GHC.Compat
import Development.IDE.Graph (Action)
import Development.IDE.Graph.Database (shakeLastBuildKeys)
import Development.IDE.Graph (Action)
import qualified Development.IDE.Graph as Graph
import Development.IDE.Graph.Database (ShakeDatabase,
shakeGetBuildEdges,
shakeGetBuildStep,
shakeGetCleanKeys)
import Development.IDE.Graph.Internal.Types (Result (resultBuilt, resultChanged, resultVisited),
Step (Step))
import qualified Development.IDE.Graph.Internal.Types as Graph
import Development.IDE.Types.Action
import Development.IDE.Types.HscEnvEq (HscEnvEq (hscEnv))
import Development.IDE.Types.Location (fromUri)
import GHC.Generics (Generic)
import Ide.Plugin.Config (CheckParents)
import Development.IDE.Types.HscEnvEq (HscEnvEq (hscEnv))
import Development.IDE.Types.Location (fromUri)
import GHC.Generics (Generic)
import Ide.Plugin.Config (CheckParents)
import Ide.Types
import qualified Language.LSP.Server as LSP
import qualified Language.LSP.Server as LSP
import Language.LSP.Types
import System.Time.Extra

Expand All @@ -48,7 +55,10 @@ data TestRequest
| GetShakeSessionQueueCount -- ^ :: Number
| WaitForShakeQueue -- ^ Block until the Shake queue is empty. Returns Null
| WaitForIdeRule String Uri -- ^ :: WaitForIdeRuleResult
| GetLastBuildKeys -- ^ :: [String]
| GetBuildKeysVisited -- ^ :: [(String]
| GetBuildKeysBuilt -- ^ :: [(String]
| GetBuildKeysChanged -- ^ :: [(String]
| GetBuildEdgesCount -- ^ :: Int
| GarbageCollectDirtyKeys CheckParents Age -- ^ :: [String] (list of keys collected)
| GetStoredKeys -- ^ :: [String] (list of keys in store)
| GetFilesOfInterest -- ^ :: [FilePath]
Expand Down Expand Up @@ -98,9 +108,18 @@ testRequestHandler s (WaitForIdeRule k file) = liftIO $ do
success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) nfp
let res = WaitForIdeRuleResult <$> success
return $ bimap mkResponseError toJSON res
testRequestHandler s GetLastBuildKeys = liftIO $ do
keys <- shakeLastBuildKeys $ shakeDb s
testRequestHandler s GetBuildKeysBuilt = liftIO $ do
keys <- getDatabaseKeys resultBuilt $ shakeDb s
return $ Right $ toJSON $ map show keys
testRequestHandler s GetBuildKeysChanged = liftIO $ do
keys <- getDatabaseKeys resultChanged $ shakeDb s
return $ Right $ toJSON $ map show keys
testRequestHandler s GetBuildKeysVisited = liftIO $ do
keys <- getDatabaseKeys resultVisited $ shakeDb s
return $ Right $ toJSON $ map show keys
testRequestHandler s GetBuildEdgesCount = liftIO $ do
count <- shakeGetBuildEdges $ shakeDb s
return $ Right $ toJSON count
testRequestHandler s (GarbageCollectDirtyKeys parents age) = do
res <- liftIO $ runAction "garbage collect dirty" s $ garbageCollectDirtyKeysOlderThan age parents
return $ Right $ toJSON $ map show res
Expand All @@ -111,6 +130,14 @@ testRequestHandler s GetFilesOfInterest = do
ff <- liftIO $ getFilesOfInterest s
return $ Right $ toJSON $ map fromNormalizedFilePath $ HM.keys ff

getDatabaseKeys :: (Graph.Result -> Step)
-> ShakeDatabase
-> IO [Graph.Key]
getDatabaseKeys field db = do
keys <- shakeGetCleanKeys db
step <- shakeGetBuildStep db
return [ k | (k, res) <- keys, field res == Step step]

mkResponseError :: Text -> ResponseError
mkResponseError msg = ResponseError InvalidRequest msg Nothing

Expand Down
32 changes: 24 additions & 8 deletions ghcide/test/src/Development/IDE/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ module Development.IDE.Test
, standardizeQuotes
, flushMessages
, waitForAction
, getLastBuildKeys
, getInterfaceFilesDir
, garbageCollectDirtyKeys
, getFilesOfInterest
Expand All @@ -30,7 +29,7 @@ module Development.IDE.Test
, getStoredKeys
, waitForCustomMessage
, waitForGC
) where
,getBuildKeysBuilt,getBuildKeysVisited,getBuildKeysChanged,getBuildEdgesCount) where

import Control.Applicative.Combinators
import Control.Lens hiding (List)
Expand Down Expand Up @@ -182,23 +181,40 @@ canonicalizeUri uri = filePathToUri <$> canonicalizePath (fromJust (uriToFilePat
diagnostic :: Session (NotificationMessage TextDocumentPublishDiagnostics)
diagnostic = LspTest.message STextDocumentPublishDiagnostics

callTestPlugin :: (A.FromJSON b) => TestRequest -> Session b
callTestPlugin cmd = do
tryCallTestPlugin :: (A.FromJSON b) => TestRequest -> Session (Either ResponseError b)
tryCallTestPlugin cmd = do
let cm = SCustomMethod "test"
waitId <- sendRequest cm (A.toJSON cmd)
ResponseMessage{_result} <- skipManyTill anyMessage $ responseForId cm waitId
return $ case _result of
Left (ResponseError t err _) -> error $ show t <> ": " <> T.unpack err
Left e -> Left e
Right json -> case A.fromJSON json of
A.Success a -> a
A.Success a -> Right a
A.Error e -> error e

callTestPlugin :: (A.FromJSON b) => TestRequest -> Session b
callTestPlugin cmd = do
res <- tryCallTestPlugin cmd
case res of
Left (ResponseError t err _) -> error $ show t <> ": " <> T.unpack err
Right a -> pure a


waitForAction :: String -> TextDocumentIdentifier -> Session WaitForIdeRuleResult
waitForAction key TextDocumentIdentifier{_uri} =
callTestPlugin (WaitForIdeRule key _uri)

getLastBuildKeys :: Session [T.Text]
getLastBuildKeys = callTestPlugin GetLastBuildKeys
getBuildKeysBuilt :: Session (Either ResponseError [T.Text])
getBuildKeysBuilt = tryCallTestPlugin GetBuildKeysBuilt

getBuildKeysVisited :: Session (Either ResponseError [T.Text])
getBuildKeysVisited = tryCallTestPlugin GetBuildKeysVisited

getBuildKeysChanged :: Session (Either ResponseError [T.Text])
getBuildKeysChanged = tryCallTestPlugin GetBuildKeysChanged

getBuildEdgesCount :: Session (Either ResponseError Int)
getBuildEdgesCount = tryCallTestPlugin GetBuildEdgesCount

getInterfaceFilesDir :: TextDocumentIdentifier -> Session FilePath
getInterfaceFilesDir TextDocumentIdentifier{_uri} = callTestPlugin (GetInterfaceFilesDir _uri)
Expand Down
3 changes: 1 addition & 2 deletions hls-graph/hls-graph.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ library
Development.IDE.Graph.Classes
Development.IDE.Graph.Database
Development.IDE.Graph.Rule

other-modules:
Development.IDE.Graph.Internal.Action
Development.IDE.Graph.Internal.Options
Development.IDE.Graph.Internal.Rules
Expand All @@ -55,6 +53,7 @@ library

hs-source-dirs: src
build-depends:
, aeson
, async
, base >=4.12 && <5
, bytestring
Expand Down
26 changes: 13 additions & 13 deletions hls-graph/src/Development/IDE/Graph/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ module Development.IDE.Graph.Database(
shakeRunDatabaseForKeys,
shakeProfileDatabase,
shakeGetBuildStep,
shakeGetDatabaseKeys,
shakeGetDirtySet,
shakeLastBuildKeys
) where
shakeGetCleanKeys
,shakeGetBuildEdges) where
import Data.Dynamic
import Data.IORef (readIORef)
import Data.Maybe
Expand Down Expand Up @@ -48,11 +47,6 @@ shakeGetDirtySet :: ShakeDatabase -> IO [(Key, Int)]
shakeGetDirtySet (ShakeDatabase _ _ db) =
fmap snd <$> Development.IDE.Graph.Internal.Database.getDirtySet db

-- | Returns ann approximation of the database keys,
-- annotated with how long ago (in # builds) they were visited
shakeGetDatabaseKeys :: ShakeDatabase -> IO [(Key, Int)]
shakeGetDatabaseKeys (ShakeDatabase _ _ db) = getKeysAndVisitAge db

-- | Returns the build number
shakeGetBuildStep :: ShakeDatabase -> IO Int
shakeGetBuildStep (ShakeDatabase _ _ db) = do
Expand All @@ -78,9 +72,15 @@ shakeRunDatabaseForKeys keysChanged (ShakeDatabase lenAs1 as1 db) as2 = do
shakeProfileDatabase :: ShakeDatabase -> FilePath -> IO ()
shakeProfileDatabase (ShakeDatabase _ _ s) file = writeProfile file s

-- | Returns the set of keys built in the most recent step
shakeLastBuildKeys :: ShakeDatabase -> IO [Key]
shakeLastBuildKeys (ShakeDatabase _ _ db) = do
-- | Returns the clean keys in the database
shakeGetCleanKeys :: ShakeDatabase -> IO [(Key, Result )]
shakeGetCleanKeys (ShakeDatabase _ _ db) = do
keys <- Ids.elems $ databaseValues db
return [ (k,res) | (k, Clean res) <- keys]

-- | Returns the total count of edges in the build graph
shakeGetBuildEdges :: ShakeDatabase -> IO Int
shakeGetBuildEdges (ShakeDatabase _ _ db) = do
keys <- Ids.elems $ databaseValues db
step <- readIORef $ databaseStep db
return [ k | (k, Clean res) <- keys, resultBuilt res == step ]
let ress = mapMaybe (getResult . snd) keys
return $ sum $ map (length . getResultDepsDefault [] . resultDeps) ress
Loading