Skip to content

Stream the GHC eventlog and control RTS monitoring over a socket.

Notifications You must be signed in to change notification settings

well-typed/eventlog-socket

Repository files navigation

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.

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.

Note

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

Note

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 are two reasons you may want to instrument your application from C:

  1. 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.

  2. You want to register custom commands for use with the control command protocol. Custom commands are currently only supported from C. For details, see Control Commands.

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

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

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

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 three built-in commands:

  1. The startHeapProfiling control command starts heap profiling.
  2. The stopHeapProfiling control command stops heap profiling.
  3. 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 3.

  1. The message for startHeapProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x03.
  2. The message for stopHeapProfiling is \xF0\x9E\x97\x8C\x00\x15eventlog-socket\x04.
  3. 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. Currently, custom control commands can only be registered via the C API. Registration is a two-step process:

  1. Register a namespace. This must be a string of between 1 and 255 characters. 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.

Commands can be passed some user data at registration time. This is intended to let you defines multiple different commands using the same handler.

The following snippet defines the register_greeters function, which registers both the greet_c and greet_haskell commands using the greeter callback.

#include <stdio.h>
#include <stdlib.h>
#include <eventlog_socket.h>

static void greeter(const EventlogSocketControlNamespace *const namespace,
                       const EventlogSocketControlCommandId command_id,
                       const void *user_data) {
  (void) namespace;
  (void) command_id;
  printf("Hello, %s!", (const char*)user_data);
}

bool register_greeters(void) {
  // Use the name of my Haskell package:
  const char* const my_namespace = "my-app";

  // Allocate space for the namespace object:
  EventlogSocketControlNamespace *namespace = NULL;

  // Try to register the namespace:
  const EventlogSocketStatus namespace_status =
    eventlog_socket_control_register_namespace(
      strlen(my_namespace), my_namespace, &namespace);

  // Handle any errors:
  if (namespace_status.ess_status_code != EVENTLOG_SOCKET_OK) {
    char *errmsg = eventlog_socket_strerror(namespace_status);
    if (errmsg != NULL) {
      fprintf(stderr, "ERROR: %s", errmsg);
      free(errmsg);
    }
    return false;
  }
  // Otherwise, namespace contains a valid object.

  // Use the first available non-zero command ID for greet_c:
  const uint8_t greet_c_id = 1;

  // Allocate data for the greet_c command, if needed:
  static const char * const greet_c_data = "C";

  // Try to register the greet_c command:
  const EventlogSocketStatus greet_c_status =
    eventlog_socket_control_register_command(
      namespace, greet_c_id, greeter, greet_c_data);

  // Handle any errors:
  if (greet_c_status.ess_status_code != EVENTLOG_SOCKET_OK) {
    char *errmsg = eventlog_socket_strerror(greet_c_status);
    if (errmsg != NULL) {
      fprintf(stderr, "ERROR: %s", errmsg);
      free(errmsg);
    }
    return false;
  }
  // Otherwise, the command is registered.

  // Use the next available command ID for greet_haskell:
  const uint8_t greet_haskell_id = 2;

  // Allocate data for the greet_haskell command, if needed:
  static const char * const greet_haskell_data = "Haskell";

  // Try to register the greet_haskell command:
  const EventlogSocketStatus greet_haskell_status =
    eventlog_socket_control_register_command(
      namespace, greet_haskell_id, greeter, greet_haskell_data);

  // Handle any errors:
  if (greet_haskell_status.ess_status_code != EVENTLOG_SOCKET_OK) {
    char *errmsg = eventlog_socket_strerror(greet_haskell_status);
    if (errmsg != NULL) {
      fprintf(stderr, "ERROR: %s", errmsg);
      free(errmsg);
    }
    return false;
  }
  // Otherwise, the command is registered.

  return true;
}

For a full example using custom control commands, see examples/custom-command.

Contributing

The scripts directory contains various scripts for contributors to this repository.

The ./scripts/test-haskell.sh script runs the Haskell test suite, which is located in eventlog-socket-tests.

The ./scripts/test-python.sh scripts runs the Python test suite, which is located in eventlog-socket/tests. This test suite is not fully portable, and is primarily intended to be run on Linux machines.

The ./scripts/pre-commit.sh script runs the pre-commit hooks, which contains various formatters and linters.

The ./scripts/build-doxygen.sh and ./scripts/build-haddock.sh scripts build the documentation for the C and Haskell APIs.

About

Stream the GHC eventlog and control RTS monitoring over a socket.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 8