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
12 changes: 10 additions & 2 deletions apps/deployex_web/lib/deployex_web/live/applications/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ defmodule DeployexWeb.ApplicationsLive do
{:metrics_new_data, source_node, metric_key,
%Telemetry.Data{measurements: %{total: count, limit: limit}}},
%{assigns: %{metrics: metrics}} = socket
) do
)
when is_integer(limit) and limit > 0 do
if source_node in metrics.monitored_nodes do
current_percentage = trunc(count / limit * 100)

Expand All @@ -551,7 +552,8 @@ defmodule DeployexWeb.ApplicationsLive do
{:noreply, socket}
end

def handle_info({:update_system_info, host_info}, %{assigns: %{metrics: metrics}} = socket) do
def handle_info({:update_system_info, host_info}, %{assigns: %{metrics: metrics}} = socket)
when is_integer(host_info.memory_total) and host_info.memory_total > 0 do
# Sync ui_settings from cache to ensure NavMenu has latest state
ui_settings = UiSettings.get()

Expand All @@ -572,6 +574,12 @@ defmodule DeployexWeb.ApplicationsLive do
|> assign(:ui_settings, ui_settings)}
end

# NOTE: Ignore system info when memory_total is nil or 0 to avoid division by zero
def handle_info({:update_system_info, host_info}, socket) do
ui_settings = UiSettings.get()
{:noreply, socket |> assign(:host_info, host_info) |> assign(:ui_settings, ui_settings)}
end

def handle_info(
{:monitoring_app_updated, source_node, monitoring_apps_data},
%{assigns: %{node: node, metrics: metrics}} = socket
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule DeployexWeb.Applications.IndexTest do
import Mox

alias DeployexWeb.Fixture.Status, as: FixtureStatus
alias Host.Info

setup [
:set_mox_global,
Expand Down Expand Up @@ -490,6 +491,98 @@ defmodule DeployexWeb.Applications.IndexTest do
end
end)

describe "division by zero guards" do
@tag :capture_log
test "GET /applications handles vm metric with limit 0 without crashing", %{conn: conn} do
topic = "test-topic"

deployex =
FixtureStatus.deployex(%{
node: Node.self(),
monitoring: add_metrics([:memory, :port])
})

Deployer.StatusMock
|> expect(:monitoring, fn -> {:ok, [deployex, FixtureStatus.application()]} end)
|> expect(:subscribe, fn -> Phoenix.PubSub.subscribe(Deployer.PubSub, topic) end)
|> stub(:history_version_list, fn _name, _options -> FixtureStatus.versions() end)

{:ok, view, _html} = live(conn, ~p"/applications")

# Send a metric with limit 0 - should not crash the LiveView
zero_limit_data = %ObserverWeb.Telemetry.Data{
timestamp: :rand.uniform(2_000_000_000_000),
value: 0,
unit: " kilobyte",
tags: %{},
measurements: %{
limit: 0,
total: 0
}
}

Phoenix.PubSub.broadcast(
Deployer.PubSub,
topic,
{:metrics_new_data, Node.self(), "vm.port.total", zero_limit_data}
)

# The LiveView should still be alive and renderable
assert Process.alive?(view.pid)
assert render(view) =~ "Resource Monitoring"
end

@tag :capture_log
test "GET /applications handles nil memory_total without crashing", %{conn: conn} do
topic = "test-topic"

deployex =
FixtureStatus.deployex(%{
node: Node.self(),
monitoring: add_metrics([:memory])
})

Deployer.StatusMock
|> expect(:monitoring, fn -> {:ok, [deployex, FixtureStatus.application()]} end)
|> expect(:subscribe, fn -> Phoenix.PubSub.subscribe(Deployer.PubSub, topic) end)
|> stub(:history_version_list, fn _name, _options -> FixtureStatus.versions() end)

{:ok, view, _html} = live(conn, ~p"/applications")

# Send system info with nil memory_total - should not crash the LiveView
send(view.pid, {:update_system_info, %Info{memory_total: nil, memory_free: nil}})

# The LiveView should still be alive and renderable
assert Process.alive?(view.pid)
assert render(view) =~ "Resource Monitoring"
end

@tag :capture_log
test "GET /applications handles zero memory_total without crashing", %{conn: conn} do
topic = "test-topic"

deployex =
FixtureStatus.deployex(%{
node: Node.self(),
monitoring: add_metrics([:memory])
})

Deployer.StatusMock
|> expect(:monitoring, fn -> {:ok, [deployex, FixtureStatus.application()]} end)
|> expect(:subscribe, fn -> Phoenix.PubSub.subscribe(Deployer.PubSub, topic) end)
|> stub(:history_version_list, fn _name, _options -> FixtureStatus.versions() end)

{:ok, view, _html} = live(conn, ~p"/applications")

# Send system info with 0 memory_total - should not crash the LiveView
send(view.pid, {:update_system_info, %Info{memory_total: 0, memory_free: 0}})

# The LiveView should still be alive and renderable
assert Process.alive?(view.pid)
assert render(view) =~ "Resource Monitoring"
end
end

defp add_metrics(metrics, enabled \\ true) do
Enum.map(metrics, fn metric ->
{metric,
Expand Down
Loading