Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CHANGELOG (v0.1.X)

## 0.1.13 ()

### Backwards incompatible changes for 0.1.12
* None

### Bug fixes
* None

### Enhancements
* [[`PR-30`](https://github.com/thiagoesteves/observer_web/pull/30)] Adding configurable timeout for fetching specific states.

## 0.1.12 🚀 (2025-10-12)

### Backwards incompatible changes for 0.1.11
Expand Down
22 changes: 12 additions & 10 deletions lib/observer_web/apps/process.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule ObserverWeb.Apps.Process do
alias ObserverWeb.Apps.Helper
alias ObserverWeb.Rpc

@default_get_state_timeout 100

@process_full [
:registered_name,
:priority,
Expand All @@ -34,16 +36,16 @@ defmodule ObserverWeb.Apps.Process do
@doc """
Creates a complete overview of process stats based on the given `pid`.
"""
@spec info(pid :: pid()) :: :undefined | map
def info(pid) do
process_info(pid, @process_full, &structure_full/2)
@spec info(pid :: pid(), timeout :: non_neg_integer()) :: :undefined | map
def info(pid, timeout \\ @default_get_state_timeout) do
process_info(pid, @process_full, &structure_full/3, timeout)
end

@spec state(pid :: pid()) :: {:ok, any()} | {:error, String.t()}
def state(pid) do
@spec state(pid :: pid(), timeout :: non_neg_integer()) :: {:ok, any()} | {:error, String.t()}
def state(pid, timeout \\ @default_get_state_timeout) do
if pid != self() do
try do
state = :sys.get_state(pid, 100)
state = :sys.get_state(pid, timeout)
{:ok, state}
catch
_, reason ->
Expand All @@ -59,10 +61,10 @@ defmodule ObserverWeb.Apps.Process do
### ==========================================================================
### Private functions
### ==========================================================================
defp process_info(pid, information, structurer) do
defp process_info(pid, information, structurer, timeout) do
case Rpc.pinfo(pid, information) do
:undefined -> :undefined
data -> structurer.(data, pid)
data -> structurer.(data, pid, timeout)
end
end

Expand Down Expand Up @@ -93,12 +95,12 @@ defmodule ObserverWeb.Apps.Process do

# Structurers

defp structure_full(data, pid) do
defp structure_full(data, pid, timeout) do
gc = Keyword.get(data, :garbage_collection, [])
dictionary = Keyword.get(data, :dictionary)

{state, phx_lv_socket} =
case state(pid) do
case state(pid, timeout) do
{:ok, %{socket: %Phoenix.LiveView.Socket{}} = state} ->
new_state = %{state | socket: "Phoenix.LiveView.Socket", components: "hidden"}
{to_string(:io_lib.format("~tp", [new_state])), state.socket}
Expand Down
4 changes: 3 additions & 1 deletion lib/observer_web/telemetry/producer/phx_lv_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule ObserverWeb.Telemetry.Producer.PhxLvSocket do

alias ObserverWeb.Telemetry.Consumer

@default_lv_get_state_timeout 300

@type t :: %__MODULE__{
module: atom(),
event: list()
Expand Down Expand Up @@ -95,7 +97,7 @@ defmodule ObserverWeb.Telemetry.Producer.PhxLvSocket do
defp sockets_connected(sockets) do
sockets
|> Enum.reduce(0, fn socket, acc ->
case ObserverWeb.Apps.Process.state(socket) do
case ObserverWeb.Apps.Process.state(socket, @default_lv_get_state_timeout) do
{:ok, %{socket: %Phoenix.LiveView.Socket{transport_pid: transport_pid}}}
when is_pid(transport_pid) ->
acc + 1
Expand Down
2 changes: 1 addition & 1 deletion lib/web/components/metrics/phx_lv_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule Observer.Web.Components.Metrics.PhxLvSocket do
"""
end

defp liveview_regex(), do: ~r/^phoenix\.liveview\.socket\..+\.total$/
defp liveview_regex, do: ~r/^phoenix\.liveview\.socket\..+\.total$/

# NOTE: Streams are retrieved in the reverse order
defp normalize(metrics) do
Expand Down
32 changes: 27 additions & 5 deletions lib/web/pages/apps/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ defmodule Observer.Web.Apps.Page do
min="-1"
label="Initial Depth"
/>
<Core.input
field={@form[:get_state_timeout]}
type="number"
step="100"
min="100"
label="State Timeout (ms)"
/>
</.form>
</:inner_form>
<:inner_button>
Expand Down Expand Up @@ -196,8 +203,15 @@ defmodule Observer.Web.Apps.Page do
{:noreply, socket |> assign(:show_observer_options, show_observer_options)}
end

def handle_parent_event("form-update", %{"initial_tree_depth" => depth}, socket) do
{:noreply, assign(socket, form: to_form(%{"initial_tree_depth" => depth}))}
def handle_parent_event(
"form-update",
%{"initial_tree_depth" => depth, "get_state_timeout" => get_state_timeout},
socket
) do
{:noreply,
assign(socket,
form: to_form(%{"initial_tree_depth" => depth, "get_state_timeout" => get_state_timeout})
)}
end

def handle_parent_event(
Expand Down Expand Up @@ -347,10 +361,18 @@ defmodule Observer.Web.Apps.Page do
@impl Page
def handle_info(
{"request-process", %{"id" => request_id, "series_name" => series_name}},
%{assigns: %{current_selected_id: %{id_string: id_string, debouncing: debouncing}}} =
%{
assigns: %{
current_selected_id: %{id_string: id_string, debouncing: debouncing},
form: form
}
} =
socket
)
when id_string != request_id or debouncing < 0 do
get_state_timeout = form.params["get_state_timeout"] |> String.to_integer()

# IO.inspect get_state_timeout
pid? = String.contains?(request_id, "#PID<")
port? = String.contains?(request_id, "#Port<")

Expand All @@ -364,7 +386,7 @@ defmodule Observer.Web.Apps.Page do
|> :erlang.list_to_pid()

%{
info: Apps.Process.info(pid),
info: Apps.Process.info(pid, get_state_timeout),
id_string: request_id,
type: "pid",
debouncing: @tooltip_debouncing
Expand Down Expand Up @@ -462,7 +484,7 @@ defmodule Observer.Web.Apps.Page do
assign(socket, :observer_data, Map.put(observer_data, data_key, updated_data))
end

defp default_form_options, do: %{"initial_tree_depth" => "3"}
defp default_form_options, do: %{"initial_tree_depth" => "3", "get_state_timeout" => "100"}

defp node_info_new do
%{
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule ObserverWeb.MixProject do
use Mix.Project

@source_url "https://github.com/thiagoesteves/observer_web"
@version "0.1.12"
@version "0.1.13"

def project do
[
Expand Down
23 changes: 22 additions & 1 deletion test/observer_web/web/live/apps/page_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ defmodule Observer.Web.Apps.PageLiveTest do
assert html =~ "4242"
end

test "Adjust Get State Timeout", %{conn: conn} do
RpcStubber.defaults()
TelemetryStubber.defaults()

{:ok, index_live, _html} = live(conn, "/observer/applications")

html =
index_live
|> element("#apps-multi-select-toggle-options")
|> render_click()

refute html =~ "8888"

html =
index_live
|> element("#apps-update-form")
|> render_change(%{get_state_timeout: "8888"})

assert html =~ "8888"
end

test "Add/Remove Local Service + Kernel App", %{conn: conn} do
node = Node.self() |> to_string
service = Helpers.normalize_id(node)
Expand Down Expand Up @@ -218,7 +239,7 @@ defmodule Observer.Web.Apps.PageLiveTest do
assert_receive {:apps_page_pid, apps_page_pid}, 1_000

with_mock ObserverWeb.Apps.Process,
info: fn _ ->
info: fn _pid, _timeout ->
data = %{
pid: self(),
registered_name: "name",
Expand Down