MuonTrap.Daemon (muontrap v2.0.0-rc.0)

Copy Markdown View Source

Wrap an OS process in a GenServer so that it can be supervised

For example, in your children list add MuonTrap.Daemon like this:

children = [
  {MuonTrap.Daemon, ["my_server", ["--options", "foo"], [cd: "/some_directory"]]}
]

opts = [strategy: :one_for_one, name: MyApplication.Supervisor]
Supervisor.start_link(children, opts)

In the child_spec tuple, the second element is a list that corresponds to the MuonTrap.cmd/3 parameters. I.e., The first item in the list is the program to run, the second is a list of commandline arguments, and the third is a list of options. The same options as MuonTrap.cmd/3 are available with the following additions:

  • :name - Register the specified name for the daemon GenServer
  • :logger_fun - Pass a 1-arity function or t:mfargs/0 tuple to replace the default logging behavior. When set, :log_output, :log_prefix, :log_transform, and :logger_metadata will be ignored.
  • :log_output - When set, send output from the command to the Logger. Specify the log level (e.g., :debug)
  • :log_prefix - Prefix each log message with this string (defaults to the program's path)
  • :log_transform - Pass a function that takes a string and returns a string to format output from the command. Defaults to String.replace_invalid/1 on Elixir 1.16+ to avoid crashing the logger on non-UTF8 output.
  • :logger_metadata - A keyword list to merge into the process's logger metadata. The :muontrap_cmd and :muontrap_args keys are automatically added and cannot be overridden.
  • :stderr_to_stdout - When set to true, redirect stderr to stdout. Defaults to false.
  • :capture_stderr_only - When set to true, capture only stderr and ignore stdout. This is useful when you want to capture error messages but not regular output. Defaults to false.
  • :exit_status_to_reason - Optional function to convert the exit status (a number) to stop reason for the Daemon GenServer. Use if error exit codes carry information or aren't errors.
  • :wait_for - A 0-arity function that runs before the OS process is launched. Use to wait for a required resource to be available. The return value is ignored. Raise to abort the launch.

If you want to run multiple MuonTrap.Daemons under one supervisor, they'll all need unique IDs. Use Supervisor.child_spec/2 like this:

Supervisor.child_spec({MuonTrap.Daemon, ["my_server", []]}, id: :server1)

Summary

Functions

Read a cgroup v2 interface file from the daemon's cgroup

Return the daemon's writable cgroup settings

Return the daemon's cgroup path

Write a value to a cgroup v2 interface file in the daemon's cgroup

Returns a specification to start this module under a supervisor.

Return the OS pid to the muontrap executable

Start/link a deamon GenServer for the specified command.

Return statistics about the daemon

Functions

cgget(server, variable_name)

@spec cgget(GenServer.server(), binary()) ::
  {:ok, String.t()} | {:error, File.posix() | :no_cgroup}

Read a cgroup v2 interface file from the daemon's cgroup

variable_name is a v2 interface file like "memory.current" or "cpu.stat". See man 7 cgroups and the kernel's Documentation/admin-guide/cgroup-v2.rst for the full list.

Returns {:error, :no_cgroup} if the daemon wasn't started under a cgroup.

cgroup_config(server)

@spec cgroup_config(GenServer.server()) :: %{required(atom()) => term()}

Return the daemon's writable cgroup settings

Keys mirror the cgroup v2 interface file names with . replaced by _ (e.g. cpu.weight:cpu_weight, memory.swap.max:memory_swap_max). Files that aren't present (controller not enabled, knob not in this kernel) are omitted. Returns an empty map if the daemon isn't running under a cgroup.

The returned map is accepted as the :cgroup option on start_link/3/MuonTrap.cmd/3, so you can read one daemon's settings and start another with the same configuration under a fresh cgroup_path (or cgroup_base).

Possible keys:

  • :cpu_weight - integer 1..10000 (default 100)
  • :cpu_max - :max or {quota_us, period_us}
  • :cpu_idle - boolean
  • :memory_min, :memory_low - bytes
  • :memory_high, :memory_max, :memory_swap_max - bytes or :max
  • :memory_oom_group - boolean
  • :pids_max - count or :max
  • :io_weight - integer 1..10000
  • :cpuset_cpus, :cpuset_mems - range strings (e.g. "0-3,5")

See cgroup_path/1 to retrieve the cgroup path itself, and statistics/1 for the read-only stat files.

cgroup_path(server)

@spec cgroup_path(GenServer.server()) :: String.t() | nil

Return the daemon's cgroup path

Paths are relative to /sys/fs/cgroup. For example, a daemon started with cgroup_base: "muontrap" might return "muontrap/a1b2c3".

Returns nil if not running under a cgroup.

cgset(server, variable_name, value)

@spec cgset(GenServer.server(), binary(), binary()) ::
  :ok | {:error, File.posix() | :no_cgroup}

Write a value to a cgroup v2 interface file in the daemon's cgroup

Returns {:error, :no_cgroup} if the daemon wasn't started under a cgroup.

child_spec(init_arg)

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns a specification to start this module under a supervisor.

See Supervisor.

os_pid(server)

@spec os_pid(GenServer.server()) :: non_neg_integer() | :error

Return the OS pid to the muontrap executable

start_link(command, args, opts \\ [])

@spec start_link(binary(), [binary()], keyword()) :: GenServer.on_start()

Start/link a deamon GenServer for the specified command.

statistics(server)

@spec statistics(GenServer.server()) :: %{
  output_byte_count: non_neg_integer(),
  cgroup: %{optional(String.t()) => term()}
}

Return statistics about the daemon

Always-present keys:

  • :output_byte_count - bytes output by the process being run
  • :cgroup - map of cgroup v2 statistics (empty if the daemon isn't running under a cgroup)

The :cgroup map is keyed by the cgroup v2 interface file name. Files that don't exist (e.g., the controller isn't enabled, or PSI isn't compiled into the kernel) are omitted rather than reported as errors. Nested maps (flat-keyed files and PSI pressure files) also use the kernel's field names as string keys.

Possible :cgroup keys:

  • "memory.current", "memory.peak", "memory.swap.current" - bytes
  • "memory.events" - flat-keyed map ("low", "high", "max", "oom", "oom_kill", ...)
  • "memory.pressure", "cpu.pressure", "io.pressure" - PSI maps shaped like %{"some" => %{"avg10" => 0.0, "avg60" => 0.0, "avg300" => 0.0, "total" => 0}, "full" => %{...}}
  • "cpu.stat" - flat-keyed map ("usage_usec", "user_usec", "system_usec", plus "nr_periods", "nr_throttled", "throttled_usec" when cpu.max is set)
  • "pids.current", "pids.peak" - counts
  • "pids.events" - flat-keyed map (e.g. "max")
  • "cgroup.stat" - flat-keyed map with "nr_descendants", "nr_dying_descendants"

See the kernel's Documentation/admin-guide/cgroup-v2.rst for the full semantics of each file.