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 {
|
h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cpus, #mems, #disks {
|
#cpus, #mems, #disks {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpus {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cpus h2, #mems h2, #disks h2 {
|
#cpus h2, #mems h2, #disks h2 {
|
||||||
|
|
|
@ -17,7 +17,7 @@ config :tietopaketti,
|
||||||
name: System.fetch_env!("INSTANCE_NAME")
|
name: System.fetch_env!("INSTANCE_NAME")
|
||||||
},
|
},
|
||||||
update_interval: String.to_integer(System.get_env("UPDATE_INTERVAL") || "2000"),
|
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
|
# ## Using releases
|
||||||
#
|
#
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Tietopaketti.Application do
|
||||||
name: Tietopaketti.Sysmon,
|
name: Tietopaketti.Sysmon,
|
||||||
pubsub_name: Tietopaketti.PubSub,
|
pubsub_name: Tietopaketti.PubSub,
|
||||||
update_interval: Application.fetch_env!(:tietopaketti, :update_interval),
|
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)
|
# Start the Endpoint (http/https)
|
||||||
TietopakettiWeb.Endpoint
|
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
|
defmodule Disk do
|
||||||
deftypedstruct(%{
|
deftypedstruct(%{
|
||||||
id: non_neg_integer(),
|
id: non_neg_integer(),
|
||||||
name: String.t(),
|
path: String.t(),
|
||||||
total: non_neg_integer(),
|
total: non_neg_integer(),
|
||||||
used_percent: non_neg_integer()
|
used_percent: 0..100,
|
||||||
|
stats: Tietopaketti.ProcDiskstats.t(),
|
||||||
|
prev_stats: Tietopaketti.ProcDiskstats.t()
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,14 @@ defmodule Tietopaketti.Sysmon do
|
||||||
use GenServer
|
use GenServer
|
||||||
import Tietopaketti.TypedStruct
|
import Tietopaketti.TypedStruct
|
||||||
alias Tietopaketti.Sysdata
|
alias Tietopaketti.Sysdata
|
||||||
|
alias Tietopaketti.ProcDiskstats
|
||||||
|
|
||||||
defmodule Options do
|
defmodule Options do
|
||||||
deftypedstruct(%{
|
deftypedstruct(%{
|
||||||
name: GenServer.name(),
|
name: GenServer.name(),
|
||||||
pubsub_name: GenServer.name(),
|
pubsub_name: GenServer.name(),
|
||||||
update_interval: non_neg_integer(),
|
update_interval: non_neg_integer(),
|
||||||
disks_to_show: MapSet.t(String.t())
|
disks: %{optional(String.t()) => String.t()}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,7 +17,9 @@ defmodule Tietopaketti.Sysmon do
|
||||||
deftypedstruct(%{
|
deftypedstruct(%{
|
||||||
pubsub_name: GenServer.name(),
|
pubsub_name: GenServer.name(),
|
||||||
update_interval: non_neg_integer(),
|
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
|
end
|
||||||
|
|
||||||
|
@ -36,7 +39,9 @@ defmodule Tietopaketti.Sysmon do
|
||||||
%State{
|
%State{
|
||||||
pubsub_name: opts.pubsub_name,
|
pubsub_name: opts.pubsub_name,
|
||||||
update_interval: opts.update_interval,
|
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
|
end
|
||||||
|
|
||||||
|
@ -54,13 +59,23 @@ defmodule Tietopaketti.Sysmon do
|
||||||
|
|
||||||
mem_data = :memsup.get_system_memory_data()
|
mem_data = :memsup.get_system_memory_data()
|
||||||
|
|
||||||
|
disk_data = :disksup.get_disk_data()
|
||||||
|
disk_activity = load_disk_activity(state)
|
||||||
|
|
||||||
disk_data =
|
disk_data =
|
||||||
:disksup.get_disk_data()
|
for {{path, total, used}, i} <- Enum.with_index(disk_data),
|
||||||
|> Enum.filter(fn {name, _, _} -> MapSet.member?(state.disks_to_show, name) end)
|
Map.has_key?(state.blocks_by_path, path) do
|
||||||
|> Enum.with_index()
|
block = Map.fetch!(state.blocks_by_path, path)
|
||||||
|> Enum.map(fn {{name, total, used}, i} ->
|
|
||||||
%Sysdata.Disk{id: i, name: name, total: total, used_percent: used}
|
%Sysdata.Disk{
|
||||||
end)
|
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 =
|
free_memory =
|
||||||
case Keyword.get(mem_data, :available_memory, :unavailable) do
|
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})
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule TietopakettiWeb.DashLive.Index do
|
defmodule TietopakettiWeb.DashLive.Index do
|
||||||
use TietopakettiWeb, :live_view
|
use TietopakettiWeb, :live_view
|
||||||
|
alias Tietopaketti.ProcDiskstats
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
socket =
|
socket =
|
||||||
|
@ -22,6 +23,16 @@ defmodule TietopakettiWeb.DashLive.Index do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
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
|
def humanize_size(bytes, add_unit) do
|
||||||
bytes = bytes + 0.0
|
bytes = bytes + 0.0
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @sysdata.disk != [] do %>
|
<%= if @sysdata.disk != [] do %>
|
||||||
<div id="disks">
|
<div id="disks">
|
||||||
<h2>Disks</h2>
|
<h2>Disks</h2>
|
||||||
|
@ -55,15 +55,23 @@
|
||||||
<.live_component
|
<.live_component
|
||||||
module={TietopakettiWeb.Progress}
|
module={TietopakettiWeb.Progress}
|
||||||
id={"disk-progress-#{disk.id}"}
|
id={"disk-progress-#{disk.id}"}
|
||||||
label={disk.name}
|
label={"R: #{disk.path}"}
|
||||||
value={disk.used_percent}
|
value={sector_diff_bytes(disk.prev_stats, disk.stats, &reads_getter/1)}
|
||||||
max={100}
|
|
||||||
value-display={"#{disk.used_percent} % of #{humanize_size_si(
|
value-display={"#{disk.used_percent} % of #{humanize_size_si(
|
||||||
disk.total * 1024,
|
disk.total * 1024,
|
||||||
true
|
true
|
||||||
)}"}
|
)}"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -4,24 +4,31 @@ defmodule TietopakettiWeb.Progress do
|
||||||
@max_history 100
|
@max_history 100
|
||||||
|
|
||||||
def mount(socket) do
|
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
|
end
|
||||||
|
|
||||||
def update(assigns, socket) do
|
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,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(
|
|> assign(
|
||||||
max: assigns.max,
|
|
||||||
label: assigns.label,
|
label: assigns.label,
|
||||||
id: assigns.id,
|
id: assigns.id,
|
||||||
value_display: assigns[:"value-display"],
|
value_display: assigns[:"value-display"],
|
||||||
val_id: val_id + 1
|
vals: vals,
|
||||||
)
|
vals_as_list: as_list,
|
||||||
|> stream(:history, [%{id: "#{assigns.id}-vals-#{val_id}", val: assigns.value}],
|
max: max
|
||||||
at: -1,
|
|
||||||
limit: -@max_history
|
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,9 +39,13 @@ defmodule TietopakettiWeb.Progress do
|
||||||
<div class="progress-current">
|
<div class="progress-current">
|
||||||
<%= @value_display %>
|
<%= @value_display %>
|
||||||
</div>
|
</div>
|
||||||
<div id={"#{@id}-vals"} class="progress-vals" aria-hidden="true" phx-update="stream">
|
<div id={"#{@id}-vals"} class="progress-vals" aria-hidden="true">
|
||||||
<%= for {id, val} <- @streams.history, @max > 0 do %>
|
<%= for {val, i} <- Enum.with_index(@vals_as_list), @max > 0 do %>
|
||||||
<div id={id} class="progress-val" style={"height: #{Float.round(val.val / @max * 100)}%;"}>
|
<div
|
||||||
|
id={"#{@id}-vals-#{i}"}
|
||||||
|
class="progress-val"
|
||||||
|
style={"height:#{Float.round(val / @max * 100)}%;"}
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -4,7 +4,7 @@ defmodule Tietopaketti.MixProject do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :tietopaketti,
|
app: :tietopaketti,
|
||||||
version: "1.1.1",
|
version: "1.2.0",
|
||||||
elixir: "~> 1.15",
|
elixir: "~> 1.15",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
|
|
Loading…
Reference in a new issue