Last active
January 12, 2023 05:56
-
-
Save AviKav/ba7e1988a4837a5edbcab8810b0156fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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