Skip to content

Commit 9649165

Browse files
pepeiborrajneira
andauthored
Perform memory measurement on SIGUSR1 (#761)
* Perform memory measurement on SIGUSR1 * remove nub * Update the open telemetry docs * remove redundant bang Co-authored-by: Javier Neira <[email protected]>
1 parent 4086845 commit 9649165

File tree

4 files changed

+50
-27
lines changed

4 files changed

+50
-27
lines changed

ghcide/docs/opentelemetry.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ Then, you can run `ghcide`, giving it a file to dump eventlog information into.
1919
ghcide +RTS -l -ol ghcide.eventlog -RTS
2020
```
2121

22-
You can also optionally enable reporting detailed memory data with `--ot-memory-profiling`
22+
# Profiling the Shake cache
23+
24+
The flag `--ot-memory-profiling` profiles the values map repeatedly with 1s pauses in between.
2325

2426
```sh
2527
ghcide --ot-memory-profiling +RTS -A4G -l -ol ghcide.eventlog -RTS
2628
```
2729

2830
*Note:* This option, while functional, is extremely slow. You will notice this because the memory graph in the output will have datapoints spaced apart by a couple of minutes. The nursery must be big enough (-A1G or larger) or the measurements will self-abort.
2931

32+
Another way to profile the heap is by sending a USR1 signal (`kill -s USR1`) to the process.
33+
3034
## Viewing with tracy
3135

3236
After installing `opentelemetry-extra` and `tracy`, you can view the opentelementry output:

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,7 @@ shakeOpen getLspId eventer withProgress withIndefiniteProgress clientCapabilitie
406406
let ideState = IdeState{..}
407407

408408
IdeOptions{ optOTMemoryProfiling = IdeOTMemoryProfiling otProfilingEnabled } <- getIdeOptionsIO shakeExtras
409-
when otProfilingEnabled $
410-
startTelemetry logger $ state shakeExtras
409+
startTelemetry otProfilingEnabled logger $ state shakeExtras
411410

412411
return ideState
413412
where

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

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Control.Concurrent.Extra (Var, modifyVar_, newVar,
1313
readVar, threadDelay)
1414
import Control.Exception (evaluate)
1515
import Control.Exception.Safe (catch, SomeException)
16-
import Control.Monad (unless, forM_, forever, (>=>))
16+
import Control.Monad (void, when, unless, forM_, forever, (>=>))
1717
import Control.Monad.Extra (whenJust)
1818
import Control.Seq (r0, seqList, seqTuple2, using)
1919
import Data.Dynamic (Dynamic)
@@ -28,12 +28,13 @@ import Development.IDE.Core.RuleTypes (GhcSession (GhcSession),
2828
import Development.IDE.Types.Logger (logInfo, Logger, logDebug)
2929
import Development.IDE.Types.Shake (Key (..), Value, Values)
3030
import Development.Shake (Action, actionBracket, liftIO)
31+
import Ide.PluginUtils (installSigUsr1Handler)
3132
import Foreign.Storable (Storable (sizeOf))
3233
import HeapSize (recursiveSize, runHeapsize)
3334
import Language.Haskell.LSP.Types (NormalizedFilePath,
3435
fromNormalizedFilePath)
3536
import Numeric.Natural (Natural)
36-
import OpenTelemetry.Eventlog (addEvent, beginSpan, endSpan,
37+
import OpenTelemetry.Eventlog (Synchronicity(Asynchronous), Instrument, addEvent, beginSpan, endSpan,
3738
mkValueObserver, observe,
3839
setTag, withSpan, withSpan_)
3940

@@ -71,36 +72,47 @@ otTracedAction key file success act = actionBracket
7172
unless (success res) $ setTag sp "error" "1"
7273
return res)
7374

74-
startTelemetry :: Logger -> Var Values -> IO ()
75-
startTelemetry logger stateRef = do
75+
startTelemetry :: Bool -> Logger -> Var Values -> IO ()
76+
startTelemetry allTheTime logger stateRef = do
7677
instrumentFor <- getInstrumentCached
7778
mapCountInstrument <- mkValueObserver "values map count"
7879

79-
_ <- regularly (1 * seconds) $
80-
withSpan_ "Measure length" $
81-
readVar stateRef
82-
>>= observe mapCountInstrument . length
83-
84-
_ <- regularly (1 * seconds) $ do
85-
values <- readVar stateRef
86-
let keys = nub
87-
$ Key GhcSession : Key GhcSessionDeps
88-
: [ k | (_,k) <- HMap.keys values
89-
-- do GhcSessionIO last since it closes over stateRef itself
90-
, k /= Key GhcSessionIO]
91-
++ [Key GhcSessionIO]
92-
!groupedForSharing <- evaluate (keys `using` seqList r0)
93-
measureMemory logger [groupedForSharing] instrumentFor stateRef
94-
`catch` \(e::SomeException) ->
95-
logInfo logger ("MEMORY PROFILING ERROR: " <> fromString (show e))
96-
return ()
80+
installSigUsr1Handler $ do
81+
logInfo logger "SIGUSR1 received: performing memory measurement"
82+
performMeasurement logger stateRef instrumentFor mapCountInstrument
83+
84+
when allTheTime $ void $ regularly (1 * seconds) $
85+
performMeasurement logger stateRef instrumentFor mapCountInstrument
9786
where
9887
seconds = 1000000
9988

10089
regularly :: Int -> IO () -> IO (Async ())
10190
regularly delay act = async $ forever (act >> threadDelay delay)
10291

103-
{-# ANN startTelemetry ("HLint: ignore Use nubOrd" :: String) #-}
92+
93+
performMeasurement ::
94+
Logger ->
95+
Var (HMap.HashMap (NormalizedFilePath, Key) (Value Dynamic)) ->
96+
(Maybe Key -> IO OurValueObserver) ->
97+
Instrument 'Asynchronous a m' ->
98+
IO ()
99+
performMeasurement logger stateRef instrumentFor mapCountInstrument = do
100+
withSpan_ "Measure length" $ readVar stateRef >>= observe mapCountInstrument . length
101+
102+
values <- readVar stateRef
103+
let keys = Key GhcSession
104+
: Key GhcSessionDeps
105+
: [ k | (_,k) <- HMap.keys values
106+
-- do GhcSessionIO last since it closes over stateRef itself
107+
, k /= Key GhcSession
108+
, k /= Key GhcSessionDeps
109+
, k /= Key GhcSessionIO
110+
] ++ [Key GhcSessionIO]
111+
groupedForSharing <- evaluate (keys `using` seqList r0)
112+
measureMemory logger [groupedForSharing] instrumentFor stateRef
113+
`catch` \(e::SomeException) ->
114+
logInfo logger ("MEMORY PROFILING ERROR: " <> fromString (show e))
115+
104116

105117
type OurValueObserver = Int -> IO ()
106118

hls-plugin-api/src/Ide/PluginUtils.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module Ide.PluginUtils
1818
fullRange,
1919
mkLspCommand,
2020
mkLspCmdId,
21-
allLspCmdIds,allLspCmdIds')
21+
allLspCmdIds,allLspCmdIds',installSigUsr1Handler)
2222
where
2323

2424

@@ -35,13 +35,15 @@ import Language.Haskell.LSP.Types.Capabilities
3535
#ifdef mingw32_HOST_OS
3636
import qualified System.Win32.Process as P (getCurrentProcessId)
3737
#else
38+
import System.Posix.Signals
3839
import qualified System.Posix.Process as P (getProcessID)
3940
#endif
4041
import qualified Data.Aeson as J
4142
import qualified Data.Default
4243
import qualified Data.Map.Strict as Map
4344
import Ide.Plugin.Config
4445
import qualified Language.Haskell.LSP.Core as LSP
46+
import Control.Monad (void)
4547

4648
-- ---------------------------------------------------------------------
4749

@@ -246,8 +248,14 @@ getPid :: IO T.Text
246248
getPid = T.pack . show <$> getProcessID
247249

248250
getProcessID :: IO Int
251+
installSigUsr1Handler :: IO () -> IO ()
252+
249253
#ifdef mingw32_HOST_OS
250254
getProcessID = fromIntegral <$> P.getCurrentProcessId
255+
installSigUsr1Handler _ = return ()
256+
251257
#else
252258
getProcessID = fromIntegral <$> P.getProcessID
259+
260+
installSigUsr1Handler h = void $ installHandler sigUSR1 (Catch h) Nothing
253261
#endif

0 commit comments

Comments
 (0)