Add disk activity
This commit is contained in:
parent
7646c5ec3c
commit
c2ae057124
10 changed files with 168 additions and 35 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
72
lib/tietopaketti/proc_diskstats.ex
Normal file
72
lib/tietopaketti/proc_diskstats.ex
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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>
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue