Add disk activity

This commit is contained in:
Mikko Ahlroth 2023-09-02 23:34:12 +03:00
parent 7646c5ec3c
commit c2ae057124
10 changed files with 168 additions and 35 deletions

View file

@ -10,18 +10,18 @@ h1 {
h2 {
font-size: 1.5rem;
margin-top: 2rem;
margin-bottom: 1rem;
}
h2:first-child {
margin-top: 0;
}
#cpus, #mems, #disks {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 2rem;
}
#cpus {
margin-top: 0;
}
#cpus h2, #mems h2, #disks h2 {

View file

@ -17,7 +17,7 @@ config :tietopaketti,
name: System.fetch_env!("INSTANCE_NAME")
},
update_interval: String.to_integer(System.get_env("UPDATE_INTERVAL") || "2000"),
disks_to_show: String.split(System.get_env("DISKS_TO_SHOW", ""), ",")
disks: Jason.decode!(System.get_env("DISKS", "{}"))
# ## Using releases
#

View file

@ -15,7 +15,7 @@ defmodule Tietopaketti.Application do
name: Tietopaketti.Sysmon,
pubsub_name: Tietopaketti.PubSub,
update_interval: Application.fetch_env!(:tietopaketti, :update_interval),
disks_to_show: MapSet.new(Application.fetch_env!(:tietopaketti, :disks_to_show))
disks: Application.fetch_env!(:tietopaketti, :disks)
}},
# Start the Endpoint (http/https)
TietopakettiWeb.Endpoint

View file

@ -0,0 +1,72 @@
defmodule Tietopaketti.ProcDiskstats do
import Tietopaketti.TypedStruct
@fields 17
deftypedstruct(%{
block: String.t(),
reads_completed: {non_neg_integer(), 0},
reads_merged: {non_neg_integer(), 0},
sectors_read: {non_neg_integer(), 0},
milliseconds_reading: {non_neg_integer(), 0},
writes_completed: {non_neg_integer(), 0},
writes_merged: {non_neg_integer(), 0},
sectors_written: {non_neg_integer(), 0},
milliseconds_writing: {non_neg_integer(), 0},
ios_in_progress: {non_neg_integer(), 0},
milliseconds_doing_io: {non_neg_integer(), 0},
weighted_milliseconds_doing_io: {non_neg_integer(), 0},
discards_completed: {non_neg_integer(), 0},
discards_merged: {non_neg_integer(), 0},
sectors_discarded: {non_neg_integer(), 0},
milliseconds_discarding: {non_neg_integer(), 0},
flush_requests_completed: {non_neg_integer(), 0},
milliseconds_flushing: {non_neg_integer(), 0}
})
def sector_bytes(), do: 512
def path(), do: "/proc/diskstats"
def parse_line(line) do
parts = line |> String.split(" ") |> Enum.reject(&(&1 == "")) |> Enum.with_index()
[_, _, {block, _} | parts] = parts
for {part, i} <- parts, reduce: %__MODULE__{block: block} do
acc ->
val =
case Integer.parse(part) do
{int, _bin} -> int
:error -> 0
end
if val <= @fields - 1 + 3 do
key =
case i do
3 -> :reads_completed
4 -> :reads_merged
5 -> :sectors_read
6 -> :milliseconds_readin
7 -> :writes_completed
8 -> :writes_merged
9 -> :sectors_written
10 -> :milliseconds_writin
11 -> :ios_in_progres
12 -> :milliseconds_doing_io
13 -> :weighted_milliseconds_doing_io
14 -> :discards_completed
15 -> :discards_merged
16 -> :sectors_discarded
17 -> :milliseconds_discarding
18 -> :flush_requests_completed
19 -> :milliseconds_flushing
end
struct!(acc, [{key, val}])
else
val
end
end
end
end

View file

@ -4,9 +4,11 @@ defmodule Tietopaketti.Sysdata do
defmodule Disk do
deftypedstruct(%{
id: non_neg_integer(),
name: String.t(),
path: String.t(),
total: non_neg_integer(),
used_percent: non_neg_integer()
used_percent: 0..100,
stats: Tietopaketti.ProcDiskstats.t(),
prev_stats: Tietopaketti.ProcDiskstats.t()
})
end

View file

@ -2,13 +2,14 @@ defmodule Tietopaketti.Sysmon do
use GenServer
import Tietopaketti.TypedStruct
alias Tietopaketti.Sysdata
alias Tietopaketti.ProcDiskstats
defmodule Options do
deftypedstruct(%{
name: GenServer.name(),
pubsub_name: GenServer.name(),
update_interval: non_neg_integer(),
disks_to_show: MapSet.t(String.t())
disks: %{optional(String.t()) => String.t()}
})
end
@ -16,7 +17,9 @@ defmodule Tietopaketti.Sysmon do
deftypedstruct(%{
pubsub_name: GenServer.name(),
update_interval: non_neg_integer(),
disks_to_show: MapSet.t(String.t())
paths_by_block: %{optional(String.t()) => String.t()},
blocks_by_path: %{optional(String.t()) => String.t()},
prev_disk_stats: %{optional(String.t()) => ProcDiskstats.t()}
})
end
@ -36,7 +39,9 @@ defmodule Tietopaketti.Sysmon do
%State{
pubsub_name: opts.pubsub_name,
update_interval: opts.update_interval,
disks_to_show: opts.disks_to_show
paths_by_block: opts.disks,
blocks_by_path: Map.new(opts.disks, fn {k, v} -> {v, k} end),
prev_disk_stats: Map.new(opts.disks, fn {k, _v} -> {k, %ProcDiskstats{block: k}} end)
}}
end
@ -54,13 +59,23 @@ defmodule Tietopaketti.Sysmon do
mem_data = :memsup.get_system_memory_data()
disk_data = :disksup.get_disk_data()
disk_activity = load_disk_activity(state)
disk_data =
:disksup.get_disk_data()
|> Enum.filter(fn {name, _, _} -> MapSet.member?(state.disks_to_show, name) end)
|> Enum.with_index()
|> Enum.map(fn {{name, total, used}, i} ->
%Sysdata.Disk{id: i, name: name, total: total, used_percent: used}
end)
for {{path, total, used}, i} <- Enum.with_index(disk_data),
Map.has_key?(state.blocks_by_path, path) do
block = Map.fetch!(state.blocks_by_path, path)
%Sysdata.Disk{
id: i,
path: path,
total: total,
used_percent: used,
stats: Map.get(disk_activity, block, %ProcDiskstats{block: block}),
prev_stats: Map.get(state.prev_disk_stats, block, %ProcDiskstats{block: block})
}
end
free_memory =
case Keyword.get(mem_data, :available_memory, :unavailable) do
@ -86,6 +101,20 @@ defmodule Tietopaketti.Sysmon do
}
Phoenix.PubSub.broadcast(state.pubsub_name, pubsub_topic(), {Tietopaketti.Sysdata, data})
state
%State{state | prev_disk_stats: disk_activity}
end
defp load_disk_activity(%State{} = state) do
with {:ok, content} <- File.read(ProcDiskstats.path()),
lines <- String.split(content, "\n") do
for line <- lines,
parsed <- ProcDiskstats.parse_line(line),
Map.has_key?(state.paths_by_block, parsed.block),
reduce: %{} do
acc -> Map.put(acc, parsed.block, parsed)
end
else
_ -> %{}
end
end
end

View file

@ -1,5 +1,6 @@
defmodule TietopakettiWeb.DashLive.Index do
use TietopakettiWeb, :live_view
alias Tietopaketti.ProcDiskstats
def mount(_params, _session, socket) do
socket =
@ -22,6 +23,16 @@ defmodule TietopakettiWeb.DashLive.Index do
{:noreply, socket}
end
def reads_getter(%ProcDiskstats{} = stats), do: stats.sectors_read
def writes_getter(%ProcDiskstats{} = stats), do: stats.sectors_written
def sector_diff_bytes(prev_stats, stats, getter) do
prev = getter.(prev_stats)
now = getter.(stats)
(now - prev) * ProcDiskstats.sector_bytes()
end
def humanize_size(bytes, add_unit) do
bytes = bytes + 0.0

View file

@ -46,7 +46,7 @@
/>
</div>
</div>
<%= if @sysdata.disk != [] do %>
<div id="disks">
<h2>Disks</h2>
@ -55,15 +55,23 @@
<.live_component
module={TietopakettiWeb.Progress}
id={"disk-progress-#{disk.id}"}
label={disk.name}
value={disk.used_percent}
max={100}
label={"R: #{disk.path}"}
value={sector_diff_bytes(disk.prev_stats, disk.stats, &reads_getter/1)}
value-display={"#{disk.used_percent} % of #{humanize_size_si(
disk.total * 1024,
true
)}"}
/>
</div>
<div class="disk">
<.live_component
module={TietopakettiWeb.Progress}
id={"disk-progress-#{disk.id}"}
label={"W: #{disk.path}"}
value={sector_diff_bytes(disk.prev_stats, disk.stats, &writes_getter/1)}
value-display={"R #{humanize_size_si(sector_diff_bytes(disk.prev_stats, disk.stats, &reads_getter/1), true)} | W #{humanize_size_si(sector_diff_bytes(disk.prev_stats, disk.stats, &writes_getter/1), true)}"}
/>
</div>
<% end %>
</div>
<% end %>

View file

@ -4,24 +4,31 @@ defmodule TietopakettiWeb.Progress do
@max_history 100
def mount(socket) do
{:ok, assign(socket, max: 0, label: "", id: "", value_display: "", val_id: 0)}
{:ok,
assign(socket,
label: "",
id: "",
value_display: "",
vals: 1..@max_history |> Enum.map(fn _ -> 0 end) |> :queue.from_list(),
max: 0
)}
end
def update(assigns, socket) do
val_id = socket.assigns.val_id
vals = :queue.drop(:queue.in(assigns.value, socket.assigns.vals))
as_list = :queue.to_list(vals)
max = Map.get(assigns, :max, Enum.max(as_list))
{:ok,
socket
|> assign(
max: assigns.max,
label: assigns.label,
id: assigns.id,
value_display: assigns[:"value-display"],
val_id: val_id + 1
)
|> stream(:history, [%{id: "#{assigns.id}-vals-#{val_id}", val: assigns.value}],
at: -1,
limit: -@max_history
vals: vals,
vals_as_list: as_list,
max: max
)}
end
@ -32,9 +39,13 @@ defmodule TietopakettiWeb.Progress do
<div class="progress-current">
<%= @value_display %>
</div>
<div id={"#{@id}-vals"} class="progress-vals" aria-hidden="true" phx-update="stream">
<%= for {id, val} <- @streams.history, @max > 0 do %>
<div id={id} class="progress-val" style={"height: #{Float.round(val.val / @max * 100)}%;"}>
<div id={"#{@id}-vals"} class="progress-vals" aria-hidden="true">
<%= for {val, i} <- Enum.with_index(@vals_as_list), @max > 0 do %>
<div
id={"#{@id}-vals-#{i}"}
class="progress-val"
style={"height:#{Float.round(val / @max * 100)}%;"}
>
</div>
<% end %>
</div>

View file

@ -4,7 +4,7 @@ defmodule Tietopaketti.MixProject do
def project do
[
app: :tietopaketti,
version: "1.1.1",
version: "1.2.0",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,