eventlog-socket: Stream GHC eventlog events to external processes.

[ bsd3, library, system ] [ Propose Tags ] [ Report a vulnerability ]

The eventlog-socket package lets you stream the GHC eventlog over a socket in realtime. This package exports both a Haskell API (from GHC.Eventlog.Socket) and a C API (from eventlog_socket.h). For most uses, the Haskell API is sufficient. However, if you want to instrument your application from a C main or install custom command handlers you need the C API.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Flags

Manual Flags

NameDescriptionDefault
debug

Enable debug logging.

Disabled
debug-verbosity-trace

Set the debug verbosity to TRACE.

Disabled
debug-verbosity-quiet

Set the debug verbosity to QUIET.

Disabled
control

Enable control commands.

Disabled
optimise-heavily

Pass -O2 to the C compiler.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.1.0, 0.1.2.0, 0.1.3.0 (info)
Change log CHANGELOG.md
Dependencies base (>=4.14 && <5) [details]
Tested with ghc ==9.2.8 || ==9.4.8 || ==9.6.7 || ==9.8.4 || ==9.10.2 || ==9.12.2
License BSD-3-Clause
Copyright (c) 2018-2026 Well-Typed
Author Eventlog Socket Contributors
Maintainer wen@well-typed.com
Uploaded by wenkokke at 2026-04-10T12:17:03Z
Category System
Source repo head: git clone https://github.com/well-typed/eventlog-socket.git(eventlog-socket)
this: git clone https://github.com/well-typed/eventlog-socket.git(tag eventlog-socket-0.1.3.0)(eventlog-socket)
Reverse Dependencies 2 direct, 0 indirect [details]
Downloads 124 total (13 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for eventlog-socket-0.1.3.0

[back to package description]

eventlog-socket

The eventlog-socket package supports streaming the GHC eventlog over Unix domain and TCP/IP sockets. It streams the GHC eventlog in the standard binary format, identical to the contents of a binary .eventlog file, which can be parsed using ghc-events. Whenever a new client connects, the event header is repeated, which guarantees that the client receives important events such as RTS_IDENTIFIER and WALL_CLOCK_TIME.

When compiled with +control the eventlog-socket package also exposes a control command server over the socket, which receives and executes builtin or pre-registered commands, which allows you to dynamically enable, e.g., heap profiling from the external monitoring process. The eventlog-socket-control package provides utilities for constructing messages for the the control command protocol used by this feature.

Getting Started

To use the code in this repository to profile your own application, follow these steps.

Add eventlog-socket as a dependency

Add eventlog-socket to the build-depends section for your executable.

executable my-app
  ...
  build-depends:
    ...
    , eventlog-socket  >=0.1 && <0.2
    ...

Instrument your application from Haskell

If you want to stream the GHC eventlog over a Unix domain socket, all you have to do is call the start function from GHC.Eventlog.Socket with the path you'd like it to use for the socket.

module Main where

import           Data.Foldable (for_)
import qualified GHC.Eventlog.Socket
import           System.Environment (lookupEnv)

main :: IO ()
main = do
  -- Start eventlog-socket on GHC_EVENTLOG_UNIX_PATH, if set:
  maybeUnixPath <- lookupEnv "GHC_EVENTLOG_UNIX_PATH"
  for_ maybeUnixPath $ \unixPath -> do
    putStrLn "Start eventlog-socket on " <> unixPath
    GHC.Eventlog.Socket.start unixPath

  -- The rest of your application:
  ...

If you also want your application to block until the client process connects to the Unix domain socket, you can use startWait.

For more detailed configuration options, including TCP/IP sockets, you should use the startWith function, which takes a socket address and socket options as its arguments.

⚠️ On most platforms, Unix domain socket paths are limited to 107 characters or less.

⚠️ If you instrument your application from Haskell, the GHC RTS will start with the default eventlog writer, which is the file writer. This means that your application will write the first few events to a file called my-app.eventlog. Usually, this is not an issue, as all initialization events are repeated every time a new client connects to the socket. However, it may give you problems if your application is stored on a read-only filesystem, such as the Nix store. To change the file path, you can use the -ol RTS option. To disable the file writer entirely, use the --null-eventlog-writer RTS option. If it's important that you capture all of the GHC eventlog, you must instrument your application from C, so that the GHC RTS is started with the eventlog-socket writer.

Instrument your application from C

The eventlog-socket package installs a C header file, eventlog_socket.h, which enables you to instrument your application from a C main function. There is one reason you may want to instrument your application from C, which is that you want to capture all of the GHC eventlog. If your application is instrumented from Haskell, it loses the first few events. See the note above.

For an example of an application instrumented from a custom C main, see examples/fibber-c-main.

Configure your application to enable the eventlog

To enable the eventlog, you must pass the -l RTS option. This can be done either at compile time or at runtime:

  • To set the flag at compile time, add the following to the ghc-options section for your executable. See Setting RTS options at compile time.

    executable my-app
      ghc-options:
        ...
        -with-rtsopts="-l"
        ...
    
  • To set the flag at runtime, call you application with explicit RTS options. This requires that the application is compiled with the -rtsopts GHC option. See Setting RTS options on the command line.

    ./my-app +RTS -l -RTS
    

Lifecycle Hooks

Every time a new client connects, the GHC RTS resends the eventlog header and initialisation events. If you develop a package that communicates over the eventlog, you may find that your package also needs to resent certain initialisation events. The eventlog-socket package provides lifecycle hooks for this purpose. There are two supported hooks:

  1. The post-startEventLogging hook triggers after each time event logging is started.
  2. The pre-endEventLogging hook triggers before each time event logging is ended.

To register a handler for lifecycle hook, use the registerHook function. For instance, the following snippet registers a hook that sends a user message event every time event logging is started.

module Main where

import Data.Foldable (for_)
import Debug.Trace (traceEventIO)
import GHC.Eventlog.Socket (Hook (..), registerHook, start)
import System.Environment (lookupEnv)

main :: IO ()
main = do
  -- Register the greeting hook:
  registerHook HookPostStartEventLogging $
    traceEventIO "Hello, new user!"

  -- Start eventlog-socket on GHC_EVENTLOG_UNIX_PATH, if set:
  maybeUnixPath <- lookupEnv "GHC_EVENTLOG_UNIX_PATH"
  for_ maybeUnixPath $ \unixPath -> do
    putStrLn "Start eventlog-socket on " <> unixPath
    start unixPath

  -- The rest of your application:
  ...

⚠️ To avoid races, it is important that lifecycle hooks are registered before eventlog-socket is started.

⚠️ The post-startEventLogging and pre-endEventLogging lifecycle hooks usually correspond to clients connecting and disconnecting, respectively. The only exception is if your application is instrumented from a C main, in which case the first post-startEventLogging hook triggers immediately after the GHC RTS is initialised. In this scenario, the first client does not trigger the post-startEventLogging hook, but subsequent clients do.

For an example of an application that registers lifecycle hooks using the Haskell API, see examples/fibber.

For an example of an application that registers lifecycle hooks using the C API, see examples/fibber-c-main.

Control Commands

When compiled with the +control feature flag, the eventlog socket supports control commands. These are messages that can be written to the eventlog socket by the client to control the RTS or execute custom control commands.

The eventlog-socket package provides five built-in commands:

  1. The startProfiling control command starts cost-centre profiling.
  2. The stopProfiling control command stops cost-centre profiling.
  3. The startHeapProfiling control command starts heap profiling.
  4. The stopHeapProfiling control command stops heap profiling.
  5. The requestHeapCensus control command requests a single heap profile.

The heap profiling commands require that the RTS is initialised with one of the -h options. See RTS options for heap profiling. If any heap profiling option is passed, heap profiling is started by default. To avoid this, pass the --no-automatic-heap-samples to the RTS in addition to the selected -h option, e.g., ./my-app +RTS -hT --no-automatic-heap-samples -RTS.

The eventlog-socket-control package provides utilities for creating control command protocol messages to write to the eventlog socket. If you want to send control commands from a different programming language, see Control Command Protocol for the specification of the binary control command protocol.

Control Command Protocol

A control command message starts with the magic byte sequence 0xF0 0x9E 0x97 0x8C, followed by the protocol version, followed by the namespace as a length-prefixed string, followed by the command ID byte. The following is an an ENBF grammar for control command messages:

(* control command protocol version 0 *)

message          = magic protocol-version namespace-len namespace command-id;
magic            = "0xF0" "0x9E" "0x97" "0x8C";
protocol-version = "0x00";
namespace-len    = byte;   (* must be between 0-255 *)
namespace        = {byte}; (* must be exactly namespace-len bytes *)
command-id       = byte;   (* commands are assigned numeric IDs *)

The built-in commands live in the "eventlog-socket" namespace and are numbered in order starting at 1.

  1. The message for startProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x01.
  2. The message for stopProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x02.
  3. The message for startHeapProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x03.
  4. The message for stopHeapProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x04.
  5. The message for requestHeapCensus is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x05.

For example, a simple Python client using the socket library could request a heap census as follows:

def request_heap_census(sock):
  sock.sendall(b"\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x05")

Any unknown control commands or incorrectly formatted messages are ignored, so you can send control commands without worry for crashing your application.

Custom Control Commands

The eventlog-socket package supports custom control commands. Custom control commands can be registered via both the Haskell and the C API. Registration is a two-step process:

  1. Register a namespace. This must be a string of between 1 and 255 bytes. By convention, this should be your Haskell package name.
  2. Register each command. This must be a number between 1 and 255. By convention, this should be the first non-zero number that's still available for this namespace.

To register a namespace, use the registerNamespace function. To register a custom command handler, use the registerCommand function. For instance, the following snippet registers two custom commands under the "greeters" namespace which greet C and Haskell, respectively.

module Main where

import Data.Foldable (for_)
import Debug.Trace (traceEventIO)
import GHC.Eventlog.Socket (Hook (..), registerHook, start)
import System.Environment (lookupEnv)

greeter :: String -> IO ()
greeter name =
  putStrLn $ "Hello, " <> name <> "!"

main :: IO ()
main = do
  -- Register the custom command handlers:
  myNamespace <- registerNamespace "greeters"
  registerCommand myNamespace (CommandId 1) (greeter "C")
  registerCommand myNamespace (CommandId 2) (greeter "Haskell")

  -- Start eventlog-socket on GHC_EVENTLOG_UNIX_PATH, if set:
  maybeUnixPath <- lookupEnv "GHC_EVENTLOG_UNIX_PATH"
  for_ maybeUnixPath $ \unixPath -> do
    putStrLn "Start eventlog-socket on " <> unixPath
    start unixPath

  -- The rest of your application:
  ...

⚠️ To avoid races, it is important that custom commands are registered before eventlog-socket is started.

For an example of an application instrumented with custom control commands using the Haskell API, see examples/custom-command.

For an example of an application instrumented with custom control commands using the C API, see examples/custom-command-c-main.