diff --git a/assets/css/app.css b/assets/css/app.css index 65a2f4d..9b32c78 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -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 { diff --git a/config/runtime.exs b/config/runtime.exs index 5bad652..19e741c 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -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 # diff --git a/lib/tietopaketti/application.ex b/lib/tietopaketti/application.ex index 38037a3..d49b7c6 100644 --- a/lib/tietopaketti/application.ex +++ b/lib/tietopaketti/application.ex @@ -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 diff --git a/lib/tietopaketti/proc_diskstats.ex b/lib/tietopaketti/proc_diskstats.ex new file mode 100644 index 0000000..6eabe2e --- /dev/null +++ b/lib/tietopaketti/proc_diskstats.ex @@ -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 diff --git a/lib/tietopaketti/sysdata.ex b/lib/tietopaketti/sysdata.ex index ee7a7d9..a0228b2 100644 --- a/lib/tietopaketti/sysdata.ex +++ b/lib/tietopaketti/sysdata.ex @@ -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 diff --git a/lib/tietopaketti/sysmon.ex b/lib/tietopaketti/sysmon.ex index 135b498..41db9ad 100644 --- a/lib/tietopaketti/sysmon.ex +++ b/lib/tietopaketti/sysmon.ex @@ -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 diff --git a/lib/tietopaketti_web/live/dash_live/index.ex b/lib/tietopaketti_web/live/dash_live/index.ex index cf41f07..f1d6e45 100644 --- a/lib/tietopaketti_web/live/dash_live/index.ex +++ b/lib/tietopaketti_web/live/dash_live/index.ex @@ -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 diff --git a/lib/tietopaketti_web/live/dash_live/index.html.heex b/lib/tietopaketti_web/live/dash_live/index.html.heex index c7e9ca6..b598506 100644 --- a/lib/tietopaketti_web/live/dash_live/index.html.heex +++ b/lib/tietopaketti_web/live/dash_live/index.html.heex @@ -46,7 +46,7 @@ /> - + <%= if @sysdata.disk != [] do %>

Disks

@@ -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 )}"} />
+
+ <.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)}"} + /> +
<% end %> <% end %> diff --git a/lib/tietopaketti_web/live/dash_live/progress.ex b/lib/tietopaketti_web/live/dash_live/progress.ex index 0b2a642..fb6744e 100644 --- a/lib/tietopaketti_web/live/dash_live/progress.ex +++ b/lib/tietopaketti_web/live/dash_live/progress.ex @@ -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
<%= @value_display %>
-