Skip to content

Commit 1fcf809

Browse files
committed
Collect metrics and expose an EKG server
1 parent 2204a16 commit 1fcf809

File tree

8 files changed

+127
-12
lines changed

8 files changed

+127
-12
lines changed

cabal-ghc901.project

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ source-repository-package
5353
subdir: dependent-sum-template
5454
-- https://github.com/obsidiansystems/dependent-sum/pull/59
5555

56+
source-repository-package
57+
type:git
58+
location: https://github.com/tvh/ekg-wai/
59+
tag: ba56333e0959f896a03f15a9f151f4a31d1bfcca
60+
-- https://github.com/tvh/ekg-wai/issues/4
61+
5662
write-ghc-environment-files: never
5763

5864
index-state: 2021-10-04T02:41:06Z
@@ -62,6 +68,14 @@ constraints:
6268
haskell-language-server -brittany -class -stylishhaskell -tactic
6369

6470
allow-newer:
71+
-- https://github.com/tibbe/ekg/issues/85
72+
ekg:base,
73+
ekg-core:base,
74+
ekg-core:ghc-prim,
75+
ekg-json:base,
76+
-- https://github.com/tvh/ekg-wai/issues/6
77+
ekg-wai:base,
78+
snap-server:base,
6579
floskell:base,
6680
floskell:ghc-prim,
6781
-- for shake-bench

cabal-ghc921.project

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ source-repository-package
4949
tag: 224f3901eaa1b32a27e097968afd4a3894efa77e
5050
-- https://github.com/pepeiborra/ghc-check/pull/14/files
5151

52+
source-repository-package
53+
type:git
54+
location: https://github.com/tvh/ekg-wai/
55+
tag: ba56333e0959f896a03f15a9f151f4a31d1bfcca
56+
-- https://github.com/tvh/ekg-wai/issues/4
57+
5258
write-ghc-environment-files: never
5359

5460
index-state: 2021-09-29T21:38:47Z
@@ -78,7 +84,12 @@ allow-newer:
7884
dependent-sum:constraints,
7985
diagrams:diagrams-core,
8086
Chart-diagrams:diagrams-core,
81-
SVGFonts:diagrams-core
87+
SVGFonts:diagrams-core,
88+
ekg:base,
89+
ekg-core:base,
90+
ekg-core:ghc-prim,
91+
ekg-json:base,
92+
ekg-wai:base
8293

8394
constraints:
8495
Agda ==2.6.1.3,

cabal.project

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,14 @@ index-state: 2021-10-04T02:41:06Z
4343
constraints:
4444
hyphenation +embed
4545

46+
source-repository-package
47+
type:git
48+
location: https://github.com/tvh/ekg-wai/
49+
tag: ba56333e0959f896a03f15a9f151f4a31d1bfcca
50+
-- https://github.com/tvh/ekg-wai/issues/4
51+
4652
allow-newer:
53+
ekg-wai:base,
4754
-- for shake-bench
4855
Chart-diagrams:diagrams-core,
4956
SVGFonts:diagrams-core

docs/contributing/contributing.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,35 @@ If you don't want to use [nix](https://nixos.org/guides/install-nix.html), you c
198198

199199
See the [tutorial](./plugin-tutorial.md) on writing a plugin in HLS.
200200

201+
## Measuring, benchmarking and tracing
202+
203+
### Metrics
204+
205+
HLS opens a metrics server on port 8000 exposing GC and ghcide metrics. The ghcide metrics currently exposed are:
206+
207+
- `ghcide.values_count`- count of build results in the store
208+
- `ghcide.database_count` - count of build keys in the store (these two would be the same in the absence of GC)
209+
- `ghcide.build_count` - build count. A key is GC'ed if it is dirty and older than 100 builds
210+
- `ghcide.dirty_keys_count` - non transitive count of dirty build keys
211+
- `ghcide.indexing_pending_count` - count of items in the indexing queue
212+
- `ghcide.exports_map_count` - count of identifiers in the exports map.
213+
214+
### Benchmarks
215+
216+
If you are touching performance sensitive code, take the time to run a differential
217+
benchmark between HEAD and master using the benchHist script. This assumes that
218+
"master" points to the upstream master.
219+
220+
Run the benchmarks with `cabal bench ghcide`.
221+
222+
It should take around 25 minutes and the results will be stored in the `ghcide/bench-results` folder. To interpret the results, see the comments in the `ghcide/bench/hist/Main.hs` module.
223+
224+
More details in [bench/README](../../ghcide/bench/README.md)
225+
226+
### Tracing
227+
228+
HLS records opentelemetry eventlog traces via [opentelemetry](https://hackage.haskell.org/package/opentelemetry). To generate the traces, build with `-eventlog` and run with `+RTS -l`. To visualize the traces, install [Tracy](https://github.com/wolfpld/tracy) and use [eventlog-to-tracy](https://hackage.haskell.org/package/opentelemetry-extra) to open the generated eventlog.
229+
201230
## Adding support for a new editor
202231

203232
Adding support for new editors is fairly easy if the editor already has good support for generic LSP-based extensions.

ghcide/ghcide.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ library
4848
dependent-map,
4949
dependent-sum,
5050
dlist,
51+
ekg-wai,
52+
ekg-core,
5153
exceptions,
5254
-- we can't use >= 1.7.10 while we have to use hlint == 3.2.*
5355
extra >= 1.7.4 && < 1.7.10,

ghcide/src/Development/IDE/Core/Service.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import qualified Language.LSP.Types as LSP
3030

3131
import Control.Monad
3232
import Development.IDE.Core.Shake
33-
import System.Environment (lookupEnv)
34-
33+
import System.Environment (lookupEnv)
34+
import System.Metrics
3535

3636
------------------------------------------------------------
3737
-- Exposed API
@@ -46,8 +46,9 @@ initialise :: Config
4646
-> VFSHandle
4747
-> HieDb
4848
-> IndexQueue
49+
-> Maybe Store
4950
-> IO IdeState
50-
initialise defaultConfig mainRule lspEnv logger debouncer options vfs hiedb hiedbChan = do
51+
initialise defaultConfig mainRule lspEnv logger debouncer options vfs hiedb hiedbChan metrics = do
5152
shakeProfiling <- do
5253
let fromConf = optShakeProfiling options
5354
fromEnv <- lookupEnv "GHCIDE_BUILD_PROFILING"
@@ -64,6 +65,7 @@ initialise defaultConfig mainRule lspEnv logger debouncer options vfs hiedb hied
6465
hiedbChan
6566
vfs
6667
(optShakeOptions options)
68+
metrics
6769
$ do
6870
addIdeGlobal $ GlobalIdeOptions options
6971
ofInterestRules

ghcide/src/Development/IDE/Core/Shake.hs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ import Development.IDE.Graph hiding (ShakeValue)
120120
import qualified Development.IDE.Graph as Shake
121121
import Development.IDE.Graph.Database (ShakeDatabase,
122122
shakeGetBuildStep,
123+
shakeGetDatabaseKeys,
123124
shakeOpenDatabase,
124125
shakeProfileDatabase,
125126
shakeRunDatabaseForKeys)
@@ -152,7 +153,7 @@ import Data.Aeson (toJSON)
152153
import qualified Data.ByteString.Char8 as BS8
153154
import Data.Coerce (coerce)
154155
import Data.Default
155-
import Data.Foldable (toList)
156+
import Data.Foldable (for_, toList)
156157
import Data.HashSet (HashSet)
157158
import qualified Data.HashSet as HSet
158159
import Data.IORef.Extra (atomicModifyIORef'_,
@@ -165,6 +166,8 @@ import HieDb.Types
165166
import Ide.Plugin.Config
166167
import qualified Ide.PluginUtils as HLS
167168
import Ide.Types (PluginId)
169+
import System.Metrics (Store, registerCounter,
170+
registerGauge)
168171

169172
-- | We need to serialize writes to the database, so we send any function that
170173
-- needs to write to the database over the channel, where it will be picked up by
@@ -328,7 +331,7 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
328331
| otherwise = do
329332
pmap <- readVar persistentKeys
330333
mv <- runMaybeT $ do
331-
liftIO $ Logger.logDebug (logger s) $ T.pack $ "LOOKUP UP PERSISTENT FOR: " ++ show k
334+
liftIO $ Logger.logDebug (logger s) $ T.pack $ "LOOKUP PERSISTENT FOR: " ++ show k
332335
f <- MaybeT $ pure $ HMap.lookup (Key k) pmap
333336
(dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f file
334337
MaybeT $ pure $ (,del,ver) <$> fromDynamic dv
@@ -491,10 +494,11 @@ shakeOpen :: Maybe (LSP.LanguageContextEnv Config)
491494
-> IndexQueue
492495
-> VFSHandle
493496
-> ShakeOptions
497+
-> Maybe Store
494498
-> Rules ()
495499
-> IO IdeState
496500
shakeOpen lspEnv defaultConfig logger debouncer
497-
shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) hiedb indexQueue vfs opts rules = mdo
501+
shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) hiedb indexQueue vfs opts metrics rules = mdo
498502

499503
us <- mkSplitUniqSupply 'r'
500504
ideNc <- newIORef (initNameCache us knownKeyNames)
@@ -543,11 +547,28 @@ shakeOpen lspEnv defaultConfig logger debouncer
543547
IdeOptions
544548
{ optOTMemoryProfiling = IdeOTMemoryProfiling otProfilingEnabled
545549
, optProgressStyle
550+
, optCheckParents
546551
} <- getIdeOptionsIO shakeExtras
547552

548553
void $ startTelemetry shakeDb shakeExtras
549554
startProfilingTelemetry otProfilingEnabled logger $ state shakeExtras
550555

556+
checkParents <- optCheckParents
557+
for_ metrics $ \store -> do
558+
let readValuesCounter = fromIntegral . countRelevantKeys checkParents . HMap.keys <$> readVar (state shakeExtras)
559+
readDirtyKeys = fromIntegral . countRelevantKeys checkParents . HSet.toList <$> readIORef (dirtyKeys shakeExtras)
560+
readIndexPending = fromIntegral . HMap.size <$> readTVarIO (indexPending $ hiedbWriter shakeExtras)
561+
readExportsMap = fromIntegral . HMap.size . getExportsMap <$> readVar (exportsMap shakeExtras)
562+
readDatabaseCount = fromIntegral . countRelevantKeys checkParents . map fst <$> shakeGetDatabaseKeys shakeDb
563+
readDatabaseStep = fromIntegral <$> shakeGetBuildStep shakeDb
564+
565+
registerGauge "ghcide.values_count" readValuesCounter store
566+
registerGauge "ghcide.dirty_keys_count" readDirtyKeys store
567+
registerGauge "ghcide.indexing_pending_count" readIndexPending store
568+
registerGauge "ghcide.exports_map_count" readExportsMap store
569+
registerGauge "ghcide.database_count" readDatabaseCount store
570+
registerCounter "ghcide.num_builds" readDatabaseStep store
571+
551572
return ideState
552573

553574
startTelemetry :: ShakeDatabase -> ShakeExtras -> IO (Async ())

ghcide/src/Development/IDE/Main.hs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ module Development.IDE.Main
88
,commandP
99
,defaultMain
1010
,testing) where
11+
import Control.Concurrent (killThread)
12+
import Control.Concurrent.Async (async, waitCatch)
1113
import Control.Concurrent.Extra (newLock, readVar,
1214
withLock,
1315
withNumCapabilities)
1416
import Control.Exception.Safe (Exception (displayException),
15-
catchAny)
16-
import Control.Monad.Extra (concatMapM, unless,
17-
when)
17+
catchAny, onException)
18+
import Control.Monad.Extra (concatMapM, join,
19+
unless, when)
1820
import qualified Data.Aeson.Encode.Pretty as A
1921
import Data.Default (Default (def))
2022
import Data.Foldable (traverse_)
@@ -29,6 +31,7 @@ import Data.Text.Encoding (encodeUtf8)
2931
import qualified Data.Text.IO as T
3032
import Data.Text.Lazy.Encoding (decodeUtf8)
3133
import qualified Data.Text.Lazy.IO as LT
34+
import Data.Traversable (for)
3235
import Data.Typeable (typeOf)
3336
import Data.Word (Word16)
3437
import Development.IDE (Action, GhcVersion (..),
@@ -115,6 +118,9 @@ import System.IO (BufferMode (LineBufferin
115118
hSetBuffering,
116119
hSetEncoding, stderr,
117120
stdin, stdout, utf8)
121+
import qualified System.Metrics as Monitoring
122+
import System.Remote.Monitoring.Wai
123+
import qualified System.Remote.Monitoring.Wai as Monitoring
118124
import System.Time.Extra (offsetTime,
119125
showDuration)
120126
import Text.Printf (printf)
@@ -181,6 +187,7 @@ data Arguments = Arguments
181187
, argsHandleIn :: IO Handle
182188
, argsHandleOut :: IO Handle
183189
, argsThreads :: Maybe Natural
190+
, argsMonitoringPort :: Maybe Natural
184191
}
185192

186193
instance Default Arguments where
@@ -219,6 +226,7 @@ defaultArguments priority = Arguments
219226
-- the language server tests without the redirection.
220227
putStr " " >> hFlush stdout
221228
return newStdout
229+
, argsMonitoringPort = Just 8000
222230
}
223231

224232
testing :: Arguments
@@ -310,6 +318,24 @@ defaultMain Arguments{..} = do
310318
hPutStrLn stderr $
311319
"Currently, HLS supports GHC 9 only partially. "
312320
<> "See [issue #297](https://github.com/haskell/haskell-language-server/issues/297) for more detail."
321+
322+
server <- fmap join $ for argsMonitoringPort $ \p -> do
323+
store <- Monitoring.newStore
324+
let startServer = Monitoring.forkServerWith store "localhost" (fromIntegral p)
325+
-- this can fail if the port is busy, throwing an async exception back to us
326+
-- to handle that, wrap the server thread in an async
327+
mb_server <- async startServer >>= waitCatch
328+
case mb_server of
329+
Right s -> do
330+
logInfo logger $ T.pack $
331+
"Started monitoring server on port " <> show p
332+
return $ Just s
333+
Left e -> do
334+
logInfo logger $ T.pack $
335+
"Unable to bind monitoring server on port "
336+
<> show p <> ":" <> show e
337+
return Nothing
338+
313339
initialise
314340
argsDefaultHlsConfig
315341
rules
@@ -320,6 +346,9 @@ defaultMain Arguments{..} = do
320346
vfs
321347
hiedb
322348
hieChan
349+
(Monitoring.serverMetricStore <$> server)
350+
`onException`
351+
traverse_ (killThread . serverThreadId) server
323352
Check argFiles -> do
324353
dir <- IO.getCurrentDirectory
325354
dbLoc <- getHieDbLoc dir
@@ -352,7 +381,7 @@ defaultMain Arguments{..} = do
352381
, optCheckProject = pure False
353382
, optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins
354383
}
355-
ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan
384+
ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan Nothing
356385
shakeSessionInit ide
357386
registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing)
358387

@@ -403,7 +432,7 @@ defaultMain Arguments{..} = do
403432
, optCheckProject = pure False
404433
, optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins
405434
}
406-
ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan
435+
ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan Nothing
407436
shakeSessionInit ide
408437
registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing)
409438
c ide

0 commit comments

Comments
 (0)