Repository: livebook-dev/kino
Pull request #449 (2024-06-22 14:31:52)

Show worker/supervisor ids in app supervision graph
Hi there!

TLDR; I added labels to the process nodes to make it more clear what the processes represent.

I was using `Kino.Process` in Livebook to try and figure out how some elements of the Membrane RTC Engine work. I won't go into the details, but suffice to say this is a foreign code base to me, and it wasn't clear from the get go:

* What the supervision tree looks like.
* How it the internal genservers relate to each other.
* How that ties into my phoenix app.

So I was poking around blindly until I got my RTC Engine's process's graphed with Kino. This is what it looked like for reference:
![without_initial_call](https://github.com/livebook-dev/kino/assets/59554029/27bace60-521d-47e5-9874-e3fb8baf1f9e)

Cool! So now I get to see the process layout, but I'm still blind as to what all this actually is. I still don't know what Membrane is doing. To get a bit more insight, changed the label for the PID node to also print out the node's id and got this:

![with_id](https://github.com/livebook-dev/kino/assets/59554029/314f8803-15a3-4873-b3ea-27fac8b5212b)

This helped me relate my WebRTC endpoints with pid's and I also really like the insight it gives when you plot a Phoenix app's supervision tree.


Patch:
diff --git a/lib/kino/process.ex b/lib/kino/process.ex
index e091f57..e3e7abd 100644
--- a/lib/kino/process.ex
+++ b/lib/kino/process.ex
@@ -751,7 +751,7 @@ defmodule Kino.Process do
     "#{idx}[(\"`#{module_or_atom_to_string(id)}\n**_#{protection}_**`\")]:::ets"
   end
 
-  defp graph_node(%{idx: idx, pid: pid, type: type}) do
+  defp graph_node(%{idx: idx, id: id, pid: pid, type: type}) do
     type =
       if idx == 0 do
         :root
@@ -763,8 +763,16 @@ defmodule Kino.Process do
       case process_info(pid, :registered_name) do
         {:registered_name, []} ->
           case get_label(pid) do
-            :undefined -> inspect(pid)
-            process_label -> format_for_mermaid_graph_node(pid, process_label)
+            :undefined ->
+              if idx == 0 do
+                inspect(pid)
+              else
+                # Use worker/supervisor id as label
+                format_for_mermaid_graph_node(pid, id)
+              end
+
+            process_label ->
+              format_for_mermaid_graph_node(pid, process_label)
           end
 
         {:registered_name, name} ->


Test patch:
diff --git a/test/kino/process_test.exs b/test/kino/process_test.exs
index e6b32e6..e493654 100644
--- a/test/kino/process_test.exs
+++ b/test/kino/process_test.exs
@@ -20,9 +20,13 @@ defmodule Kino.ProcessTest do
       ] = Supervisor.which_children(pid)
 
       content = pid |> Kino.Process.sup_tree(render_ets_tables: true) |> mermaid()
+      agent_pid_text = :erlang.pid_to_list(agent_pid) |> List.to_string()
+
       assert content =~ "0(supervisor_parent):::root ---> 1(ets_owner):::worker"
       assert content =~ "0(supervisor_parent):::root ---> 2(ets_heir):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 3(#{inspect(agent_pid)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 3(\"Agent<br/>#{agent_pid_text}\"):::worker"
 
       assert content =~
                "1(ets_owner):::worker -- owner --> 4[(\"`test_ets_table\n**_protected_**`\")]:::ets"
@@ -33,7 +37,9 @@ defmodule Kino.ProcessTest do
       content = :supervisor_parent |> Kino.Process.sup_tree(render_ets_tables: true) |> mermaid()
       assert content =~ "0(supervisor_parent):::root ---> 1(ets_owner):::worker"
       assert content =~ "0(supervisor_parent):::root ---> 2(ets_heir):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 3(#{inspect(agent_pid)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 3(\"Agent<br/>#{agent_pid_text}\"):::worker"
 
       assert content =~
                "1(ets_owner):::worker -- owner --> 4[(\"`test_ets_table\n**_protected_**`\")]:::ets"
@@ -52,15 +58,22 @@ defmodule Kino.ProcessTest do
       ] = Supervisor.which_children(pid)
 
       content = pid |> Kino.Process.sup_tree() |> mermaid()
+      agent_pid_text = :erlang.pid_to_list(agent_pid) |> List.to_string()
       assert content =~ "0(supervisor_parent):::root ---> 1(ets_owner):::worker"
       assert content =~ "0(supervisor_parent):::root ---> 2(ets_heir):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 3(#{inspect(agent_pid)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 3(\"Agent<br/>#{agent_pid_text}\"):::worker"
+
       refute content =~ ":::ets"
 
       content = :supervisor_parent |> Kino.Process.sup_tree() |> mermaid()
       assert content =~ "0(supervisor_parent):::root ---> 1(ets_owner):::worker"
       assert content =~ "0(supervisor_parent):::root ---> 2(ets_heir):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 3(#{inspect(agent_pid)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 3(\"Agent<br/>#{agent_pid_text}\"):::worker"
+
       refute content =~ ":::ets"
     end
 
@@ -81,14 +94,19 @@ defmodule Kino.ProcessTest do
         })
 
       [_, {_, agent, _, _}] = Supervisor.which_children(pid)
+      agent_pid_text = :erlang.pid_to_list(agent) |> List.to_string()
 
       content = Kino.Process.sup_tree(pid) |> mermaid()
       assert content =~ "0(supervisor_parent):::root ---> 1(agent_child):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 2(#{inspect(agent)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 2(\"Agent<br/>#{agent_pid_text}\"):::worker"
 
       content = Kino.Process.sup_tree(:supervisor_parent) |> mermaid()
       assert content =~ "0(supervisor_parent):::root ---> 1(agent_child):::worker"
-      assert content =~ "0(supervisor_parent):::root ---> 2(#{inspect(agent)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 2(\"Agent<br/>#{agent_pid_text}\"):::worker"
     end
 
     test "shows supervision tree with children alongside non-started children" do
@@ -108,10 +126,13 @@ defmodule Kino.ProcessTest do
         })
 
       [{:not_started, :undefined, _, _}, {_, agent, _, _}] = Supervisor.which_children(pid)
+      agent_pid_text = :erlang.pid_to_list(agent) |> List.to_string()
 
       content = Kino.Process.sup_tree(pid) |> mermaid()
       assert content =~ "0(supervisor_parent):::root ---> 1(id: :not_started):::notstarted"
-      assert content =~ "0(supervisor_parent):::root ---> 2(#{inspect(agent)}):::worker"
+
+      assert content =~
+               "0(supervisor_parent):::root ---> 2(\"Agent<br/>#{agent_pid_text}\"):::worker"
     end
 
     # TODO: remove once we require Elixir v1.17.0