Skip to content

Commit f8d3442

Browse files
committed
Log live_bytes and heap_size as reported by GHC.Stats
A thread is spawned which at a prespecified interval will report the live bytes and heap size at the last major collection. Live bytes corresponds to how much live data a program has and should match closely the value reported during heap profiling. Heap size reports the total amount of memory the RTS is using, which corresponds more closely to OS memory usage. ``` [INFO] Live bytes: 367.45MB Heap size: 676.33MB ``` Closes haskell#1493 Author: Matthew Pickering <[email protected]>
1 parent acdb82e commit f8d3442

File tree

4 files changed

+66
-4
lines changed

4 files changed

+66
-4
lines changed

ghcide/ghcide.cabal

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ library
146146
Development.IDE
147147
Development.IDE.Main
148148
Development.IDE.Core.Actions
149+
Development.IDE.Main.HeapStats
149150
Development.IDE.Core.Debouncer
150151
Development.IDE.Core.FileStore
151152
Development.IDE.Core.IdeConfiguration
@@ -300,7 +301,8 @@ executable ghcide
300301
-rtsopts
301302
-- disable idle GC
302303
-- increase nursery size
303-
"-with-rtsopts=-I0 -A128M"
304+
-- Enable collection of heap statistics
305+
"-with-rtsopts="-I0 -A128M -T"
304306
main-is: Main.hs
305307
build-depends:
306308
hiedb,

ghcide/src/Development/IDE/Main.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ import Development.IDE.Types.Options (IdeGhcSession,
7777
defaultIdeOptions,
7878
optModifyDynFlags,
7979
optTesting)
80-
import Development.IDE.Types.Shake (fromKeyType)
80+
import Development.IDE.Types.Shake (Key(Key),
81+
fromKeyType)
8182
import GHC.Conc (getNumProcessors)
8283
import GHC.IO.Encoding (setLocaleEncoding)
8384
import GHC.IO.Handle (hDuplicate)
85+
import Development.IDE.Main.HeapStats (withHeapStats)
86+
import Development.Shake (action)
8487
import HIE.Bios.Cradle (findCradle)
8588
import qualified HieDb.Run as HieDb
8689
import Ide.Plugin.Config (CheckParents (NeverCheck),
@@ -240,7 +243,7 @@ stderrLogger logLevel = do
240243
T.hPutStrLn stderr $ "[" <> T.pack (show p) <> "] " <> m
241244

242245
defaultMain :: Arguments -> IO ()
243-
defaultMain Arguments{..} = do
246+
defaultMain Arguments{..} = withHeapStats argsLogger $ do
244247
setLocaleEncoding utf8
245248
pid <- T.pack . show <$> getProcessID
246249
logger <- argsLogger
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{-# LANGUAGE NumericUnderscores #-}
2+
-- | Logging utilities for reporting heap statistics
3+
module Development.IDE.Main.HeapStats ( withHeapStats ) where
4+
5+
import GHC.Stats
6+
import Development.IDE.Types.Logger (Logger, logInfo)
7+
import Control.Concurrent.Async
8+
import qualified Data.Text as T
9+
import Data.Word
10+
import Control.Monad
11+
import Control.Concurrent
12+
import Text.Printf (printf)
13+
14+
-- | Interval at which to report the latest heap statistics.
15+
heapStatsInterval :: Int
16+
heapStatsInterval = 60_000_000 -- 60s
17+
18+
-- | Report the live bytes and heap size at the last major collection.
19+
logHeapStats :: Logger -> IO ()
20+
logHeapStats l = do
21+
stats <- getRTSStats
22+
-- live_bytes is the total amount of live memory in a program
23+
-- (corresponding to the amount on a heap profile)
24+
let live_bytes = gcdetails_live_bytes (gc stats)
25+
-- heap_size is the total amount of memory the RTS is using
26+
-- this corresponds closer to OS memory usage
27+
heap_size = gcdetails_mem_in_use_bytes (gc stats)
28+
format :: Word64 -> T.Text
29+
format m = T.pack (printf "%.2fMB" (fromIntegral @Word64 @Double m / 1e6))
30+
message = "Live bytes: " <> format live_bytes <> " " <>
31+
"Heap size: " <> format heap_size
32+
logInfo l message
33+
34+
-- | An action which logs heap statistics at the 'heapStatsInterval'
35+
heapStatsThread :: Logger -> IO r
36+
heapStatsThread l = forever $ do
37+
threadDelay heapStatsInterval
38+
logHeapStats l
39+
40+
-- | A helper function which lauches the 'heapStatsThread' and kills it
41+
-- appropiately when the inner action finishes. It also checks to see
42+
-- if `-T` is enabled.
43+
withHeapStats :: Logger -> IO r -> IO r
44+
withHeapStats l k = do
45+
enabled <- getRTSStatsEnabled
46+
if enabled
47+
then do
48+
logInfo l ("Logging heap statistics every "
49+
<> T.pack (printf "%.2fs" (fromIntegral @Int @Double heapStatsInterval / 1e6)))
50+
withAsync (heapStatsThread l) (const k)
51+
else do
52+
logInfo l "Heap statistics are not enabled (RTS option -T is needed)"
53+
k

haskell-language-server.cabal

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,11 @@ executable haskell-language-server
345345
-rtsopts
346346
-- disable idle GC
347347
-- increase nursery size
348-
"-with-rtsopts=-I0 -A128M"
348+
-- Enable collection of heap statistics
349+
"-with-rtsopts=-I0 -A128M -T"
350+
-Wno-unticked-promoted-constructors
351+
if flag(pedantic)
352+
ghc-options: -Werror
349353

350354
build-depends:
351355
, aeson

0 commit comments

Comments
 (0)