Skip to content

Instantly share code, notes, and snippets.

@AviKav
Last active January 12, 2023 05:56
Show Gist options
  • Save AviKav/ba7e1988a4837a5edbcab8810b0156fb to your computer and use it in GitHub Desktop.
Save AviKav/ba7e1988a4837a5edbcab8810b0156fb to your computer and use it in GitHub Desktop.
#!/usr/bin/env elixir
# TODO: Support multiple, non-hardcoded cards and dynamic discovery
Mix.install([{:exile, "== 0.1.0"}])
# https://techbase.kde.org/Development/Tutorials/Sensors
# https://github.com/KDE/ksysguard/blob/master/ksysguardd/Porting-HOWTO
defmodule KsysRadeon do
@cmd ~w[radeontop -d -]
# TODO: How do I align the table?
@sensor_metadata %{
"gpu" => %{name: "Graphics Pipe", unit: "%", min: 0, max: 100, type: "float"},
"ee" => %{name: "Event Engine", unit: "%", min: 0, max: 100, type: "float"},
"vgt" => %{name: "Vertex Grouper + Tesselator", unit: "%", min: 0, max: 100, type: "float"},
"ta" => %{name: "Texture Addresser", unit: "%", min: 0, max: 100, type: "float"},
"sx" => %{name: "Shader Export", unit: "%", min: 0, max: 100, type: "float"},
"sh" => %{name: "Sequencer Instruction Cache", unit: "%", min: 0, max: 100, type: "float"},
"spi" => %{name: "Shader Interpolator", unit: "%", min: 0, max: 100, type: "float"},
"sc" => %{name: "Scan Converter", unit: "%", min: 0, max: 100, type: "float"},
"pa" => %{name: "Primitive Assembly", unit: "%", min: 0, max: 100, type: "float"},
"db" => %{name: "Depth Block", unit: "%", min: 0, max: 100, type: "float"},
"cb" => %{name: "Color Block", unit: "%", min: 0, max: 100, type: "float"},
"vram" => %{name: "VRAM", unit: "MB", min: 0, max: 4065, type: "integer"},
"gtt" => %{name: "GTT", unit: "MB", min: 0, max: 32110, type: "integer"},
"mclk" => %{name: "Memory Clock", unit: "Ghz", min: 0, max: 1.75, type: "float"},
"sclk" => %{name: "Shader Clock", unit: "Ghz", min: 0, max: 1.28, type: "float"}
}
def relay(sink) do
Exile.stream!(@cmd)
|> Stream.each(fn chunk -> send(sink, {:stdout, chunk}) end)
|> Stream.run()
end
defp flush(%{buffer: buffer} = acc) do
receive do
{:stdout, chunk} ->
flush(%{acc | buffer: buffer <> chunk})
after
0 -> acc
end
end
defp parse_buffer(%{buffer: buffer} = acc) do
buffer
|> String.split("\n")
# We only care about the most recent data
# Element -2 will always be the latest, complete line.
# Element -1 will always be the partial of the next line even if it's empty (i.e. buffer ends on `\n`)
|> Enum.take(-2)
|> case do
[current_line, partial] ->
acc = %{acc | buffer: partial}
# format: "1673495591.960243: bus 0a, gpu 7.50%, ee 0.00%, vgt 0.00%, ta 4.17%, sx 4.17%, sh 0.00%, spi 4.17%, sc 4.17%, pa 0.00%, db 4.17%, cb 3.33%, vram 32.99% 1341.12mb, gtt 1.30% 416.13mb, mclk 17.14% 0.300ghz, sclk 76.64% 0.981ghz"
[_timestamp, data] = String.split(current_line, ":")
String.split(data, ",")
|> Enum.map(&String.split(&1, " ", trim: true))
|> Enum.reduce(acc, fn pairing, acc ->
key = List.first(pairing)
# pairing can be `[key, percent]` or `[key, percent, absolute]`
value = List.last(pairing)
value = Regex.run(~r/[\d\.]+/, value)
# TODO: pre-populate the keys at the beginning rather than do it here?
Map.put(acc, key, value)
end)
[partial] ->
%{acc | buffer: partial}
end
end
defp tab_print(items) do
items
|> Enum.join("\t")
|> IO.puts()
end
def loop(acc) do
command = IO.gets("ksysguardd> ") |> String.trim()
cond do
command == "monitors" ->
for {key, metadata} <- @sensor_metadata, do: tab_print([key, metadata.type])
loop(acc)
String.ends_with?(command, "?") ->
sensor = String.replace_suffix(command, "?", "")
metadata = @sensor_metadata[sensor]
# IO.puts("#{metadata.name}\t#{metadata.min}\t#{metadata.max}\t#{metadata.unit}")
tab_print([metadata.name, metadata.min, metadata.max, metadata.unit])
loop(acc)
Map.has_key?(@sensor_metadata, command) ->
sensor = command
acc = flush(acc)
acc = parse_buffer(acc)
IO.puts(acc[sensor])
loop(acc)
end
end
end
sink = self()
spawn_link(fn -> KsysRadeon.relay(sink) end)
IO.puts("ksysguardd 1.2.0")
# TODO
# This can fail if we're not sent enough of the prologue but eh
# Also this delays the prompt
buffer =
receive do
{:stdout, chunk} ->
"Dumping to -, until termination.\n" <> rest = chunk
rest
end
KsysRadeon.loop(%{buffer: buffer})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment