BEAM Process Labels

elixir

Posted

A nice improvement for observability is coming in Erlang/OTP 27.0 and therefore to Elixir also. And while I didn't implement it in Erlang - I don't have enough expertise for that - I did see the need for it and pester people ask for it, which may have helped get the ball rolling.

The TL;DR is:

Details

On May 6, 2021, I wrote to the since-retired Erlang mailing list with a question.

Is there a way to add a non-unique label to a process? If not, I would like to propose that it be added.

At that time, you could basically see two types of processes in a tool like Observer:

What I wanted was a middle ground: processes that have some role we could name, like "a web request handler" or "a background job", but not a unique name.

For example, "a database connection" in the db_connection library is created using the DBConnection.Connection module, but you will always have multiple of these. In a process tree, you can probably guess what they are by their position (they are the ones on the right here):

DBConnection tree with processes on the right unlabeled

...but it would be nicer if they were labeled:

DBConnection tree with processes on the right labeled like "pool <0.461.0>" or "connection <0.467.0>"

As another example, in your application, you might want to label a LiveView with something like {:live_chat, "foo@example.com"}. The label might not be unique - that user could have multiple browsers open - but it would still be useful.

These kinds of labels are now possible! After much discussion on the email thread and a valiant attempt at an implementation by Roger Lipscombe, the feature was finally implemented by Dan Gudmundsson and is part of Erlang/OTP 27.0-rc1 (see also the Erlang Forum announcement). The second image above is a real screenshot from Observer, using OTP 27.0-rc1 and with my local copy of :db_connection, modified to add labels using :proc_lib.set_label/1.

With guidance from the Elixir Core team, I landed a PR to add Process.set_label/1 to Elixir for convenience, temporarily using Process.put/2 directly until Elixir depends on Erlang/OTP 27+. It will be available starting in Elixir 1.17.0.

A corresponding Process.get_label/1 will have to wait for an Elixir version that requires Erlang/OTP 27, or at least Erlang/OTP 26.2. Technically, on older versions of Erlang/OTP, one process could examine the process dictionary of another using Process.info(pid, :dictionary), then get the label from there. But this is a debugging technique and not recommended for production. :proc_lib.get_label/2 in Erlang/OTP 27.0 uses a feature added in Erlang/OTP 26.2 for getting a value from the dictionary of another process without retrieving its entire dictionary, like this:

{{:dictionary, :some_key}, val} =
  :erlang.process_info(some_pid, {:dictionary, :some_key})

In the meantime, you could define either or both of these label functions yourself; for example, if you want to get labels to display in your observability library but don't want to blow up for users who aren't yet using Erlang/OTP 27.

defmodule MyApp.ProcessLabels do
  def set_label(label) do
    if function_exported?(:proc_lib, :set_label, 1) do
      # Avoid a compiler warning if the function isn't
      # defined in your version of Erlang/OTP
      apply(:proc_lib, :set_label, [label])
    else
      # This entry in the process dictionary happens
      # to be where the label goes (at least for now)
      Process.put(:"$process_label", label)
      # mimic return value of `:proc_lib.set_label/1`
      :ok
    end
  end

  def get_label(pid) do
    if function_exported?(:proc_lib, :get_label, 1) do
      # Avoid a compiler warning if the function isn't
      # defined in your version of Erlang/OTP
      apply(:proc_lib, :get_label, [pid])
    else
      # mimic return value of
      # `:proc_lib.get_label/1` when none is set.
      # Don't resort to using `Process.info(pid, :dictionary)`,
      # as this is not efficient.
      :undefined
    end
  end
end

In the future, I hope that more tools and error messages can give us more context about what processes we're dealing with.

My requests to Elixir and Erlang developers: