feat(agent): host collector for /proc loadavg, meminfo, uptime
This commit is contained in:
parent
e4db0beac6
commit
ce828084c8
5 changed files with 152 additions and 0 deletions
106
agent/lib/proxmox_agent/collectors/host.ex
Normal file
106
agent/lib/proxmox_agent/collectors/host.ex
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
defmodule ProxmoxAgent.Collectors.Host do
|
||||||
|
@moduledoc """
|
||||||
|
Reads host metrics from /proc. Accepts `proc_dir:` option for testability.
|
||||||
|
Never raises — on read failure, populates `:errors` and leaves the field nil.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@type sample :: %{
|
||||||
|
hostname: String.t(),
|
||||||
|
load1: float() | nil,
|
||||||
|
load5: float() | nil,
|
||||||
|
load15: float() | nil,
|
||||||
|
mem_total_bytes: non_neg_integer() | nil,
|
||||||
|
mem_available_bytes: non_neg_integer() | nil,
|
||||||
|
mem_used_bytes: non_neg_integer() | nil,
|
||||||
|
uptime_seconds: non_neg_integer() | nil,
|
||||||
|
errors: [term()]
|
||||||
|
}
|
||||||
|
|
||||||
|
@spec collect(keyword()) :: sample()
|
||||||
|
def collect(opts \\ []) do
|
||||||
|
proc_dir = Keyword.get(opts, :proc_dir, "/proc")
|
||||||
|
|
||||||
|
{load, e1} = safe(fn -> read_loadavg(proc_dir) end, {nil, nil, nil}, :loadavg)
|
||||||
|
{mem, e2} = safe(fn -> read_meminfo(proc_dir) end, %{total: nil, available: nil}, :meminfo)
|
||||||
|
{uptime, e3} = safe(fn -> read_uptime(proc_dir) end, nil, :uptime)
|
||||||
|
|
||||||
|
total = mem.total
|
||||||
|
avail = mem.available
|
||||||
|
used = if total && avail, do: total - avail, else: nil
|
||||||
|
{load1, load5, load15} = load
|
||||||
|
|
||||||
|
%{
|
||||||
|
hostname: hostname(),
|
||||||
|
load1: load1,
|
||||||
|
load5: load5,
|
||||||
|
load15: load15,
|
||||||
|
mem_total_bytes: total,
|
||||||
|
mem_available_bytes: avail,
|
||||||
|
mem_used_bytes: used,
|
||||||
|
uptime_seconds: uptime,
|
||||||
|
errors: Enum.filter([e1, e2, e3], & &1)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe(fun, fallback, tag) do
|
||||||
|
try do
|
||||||
|
{fun.(), nil}
|
||||||
|
rescue
|
||||||
|
e -> {fallback, {tag, Exception.message(e)}}
|
||||||
|
catch
|
||||||
|
:error, reason -> {fallback, {tag, reason}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_loadavg(proc_dir) do
|
||||||
|
body = File.read!(Path.join(proc_dir, "loadavg"))
|
||||||
|
[l1, l5, l15 | _] = String.split(body, ~r/\s+/, trim: true)
|
||||||
|
{to_float(l1), to_float(l5), to_float(l15)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_meminfo(proc_dir) do
|
||||||
|
body = File.read!(Path.join(proc_dir, "meminfo"))
|
||||||
|
|
||||||
|
parsed =
|
||||||
|
body
|
||||||
|
|> String.split("\n", trim: true)
|
||||||
|
|> Enum.reduce(%{}, fn line, acc ->
|
||||||
|
case String.split(line, ~r/:\s+/, parts: 2) do
|
||||||
|
[key, val] -> Map.put(acc, key, val)
|
||||||
|
_ -> acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
%{
|
||||||
|
total: kb_to_bytes(parsed["MemTotal"]),
|
||||||
|
available: kb_to_bytes(parsed["MemAvailable"])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_uptime(proc_dir) do
|
||||||
|
body = File.read!(Path.join(proc_dir, "uptime"))
|
||||||
|
[secs | _] = String.split(body, " ", trim: true)
|
||||||
|
secs |> to_float() |> trunc()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp kb_to_bytes(nil), do: nil
|
||||||
|
|
||||||
|
defp kb_to_bytes(str) do
|
||||||
|
case Regex.run(~r/(\d+)\s*kB/, str) do
|
||||||
|
[_, kb] -> String.to_integer(kb) * 1024
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp to_float(s) do
|
||||||
|
{f, _} = Float.parse(s)
|
||||||
|
f
|
||||||
|
end
|
||||||
|
|
||||||
|
defp hostname do
|
||||||
|
case :inet.gethostname() do
|
||||||
|
{:ok, name} -> List.to_string(name)
|
||||||
|
_ -> "unknown-host"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
1
agent/test/fixtures/proc/loadavg
vendored
Normal file
1
agent/test/fixtures/proc/loadavg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0.42 0.55 0.31 3/512 12345
|
||||||
7
agent/test/fixtures/proc/meminfo
vendored
Normal file
7
agent/test/fixtures/proc/meminfo
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
MemTotal: 16384000 kB
|
||||||
|
MemFree: 2048000 kB
|
||||||
|
MemAvailable: 8192000 kB
|
||||||
|
Buffers: 256000 kB
|
||||||
|
Cached: 4096000 kB
|
||||||
|
SwapTotal: 4194304 kB
|
||||||
|
SwapFree: 4194304 kB
|
||||||
1
agent/test/fixtures/proc/uptime
vendored
Normal file
1
agent/test/fixtures/proc/uptime
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
123456.78 987654.32
|
||||||
37
agent/test/proxmox_agent/collectors/host_test.exs
Normal file
37
agent/test/proxmox_agent/collectors/host_test.exs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule ProxmoxAgent.Collectors.HostTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias ProxmoxAgent.Collectors.Host
|
||||||
|
|
||||||
|
@proc Path.expand("../../fixtures/proc", __DIR__)
|
||||||
|
|
||||||
|
test "collects load average" do
|
||||||
|
sample = Host.collect(proc_dir: @proc)
|
||||||
|
assert sample.load1 == 0.42
|
||||||
|
assert sample.load5 == 0.55
|
||||||
|
assert sample.load15 == 0.31
|
||||||
|
end
|
||||||
|
|
||||||
|
test "collects memory in bytes" do
|
||||||
|
sample = Host.collect(proc_dir: @proc)
|
||||||
|
assert sample.mem_total_bytes == 16_384_000 * 1024
|
||||||
|
assert sample.mem_available_bytes == 8_192_000 * 1024
|
||||||
|
assert sample.mem_used_bytes == sample.mem_total_bytes - sample.mem_available_bytes
|
||||||
|
end
|
||||||
|
|
||||||
|
test "collects uptime seconds" do
|
||||||
|
sample = Host.collect(proc_dir: @proc)
|
||||||
|
assert sample.uptime_seconds == 123_456
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes hostname string" do
|
||||||
|
sample = Host.collect(proc_dir: @proc)
|
||||||
|
assert is_binary(sample.hostname)
|
||||||
|
assert sample.hostname != ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "missing proc files yield :errors field, not a crash" do
|
||||||
|
sample = Host.collect(proc_dir: "/nonexistent/path/xyz")
|
||||||
|
assert sample.errors != []
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue