Skip to content

Instantly share code, notes, and snippets.

@lud
Last active January 21, 2021 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lud/f714bf300d2342ba52169b8e5dcf5baf to your computer and use it in GitHub Desktop.
Save lud/f714bf300d2342ba52169b8e5dcf5baf to your computer and use it in GitHub Desktop.
defmodule Heater do
import Kernel, except: [spawn_link: 1]
def spawn_link() do
Kernel.spawn_link(fn -> init() end)
end
defp init() do
send(self(), :heat)
loop_idle()
end
def loop_heating(tasks) do
receive do
:pause ->
IO.puts("heating paused")
end_tasks(tasks)
loop_idle()
:heat ->
loop_heating(tasks)
:stop ->
IO.puts("heater stopping, exit")
end_tasks(tasks)
end
end
def loop_idle() do
receive do
:pause ->
loop_idle()
:heat ->
IO.puts("heater starting")
loop_heating(start_tasks())
:stop ->
IO.puts("heater exit")
end
end
defp start_tasks() do
_tasks = for _ <- 1..(System.schedulers_online() + 1), do: Task.async(&infinite_task/0)
end
defp end_tasks(tasks) do
for t <- tasks, do: Task.shutdown(t)
end
defp infinite_task() do
infinite_task(0)
end
defp infinite_task(n) do
infinite_task(n + 1)
end
end
defmodule Control do
def run(max) do
{pid, ref} = spawn_monitor(fn -> init(max) end)
receive do
{:DOWN, ^ref, :process, ^pid, 0} ->
_exit_code = 0
{:DOWN, ^ref, :process, ^pid, 1} ->
IO.puts([
"error when executing powermetrics. ",
IO.ANSI.bright(),
"did you run with sudo?",
IO.ANSI.reset()
])
_exit_code = 1
{:DOWN, ^ref, :process, ^pid, exit_code} when is_integer(exit_code) ->
exit_code
{:DOWN, ^ref, :process, ^pid, :normal} ->
_exit_code = 0
{:DOWN, ^ref, :process, ^pid, reason} ->
IO.puts([
"error when executing control process: ",
inspect(reason)
])
_exit_code = 1
end
end
def init(max) do
heater = Heater.spawn_link()
loop(max, heater)
end
def loop(max, heater) do
case get_metrics() do
{temp, _} when temp >= max ->
IO.puts("Reached desired temperature")
terminate(heater)
{_, true = _fan?} ->
IO.puts("Fan is running")
terminate(heater)
{_, _} ->
send(heater, :heat)
# sleeping to not spam powermetrics
Process.sleep(1000)
loop(max, heater)
end
end
def terminate(heater) do
ref = Process.monitor(heater)
IO.puts("unlinking heater")
Process.unlink(heater)
send(heater, :stop)
receive do
{:DOWN, ^ref, :process, ^heater, :normal} -> exit(0)
{:DOWN, ^ref, :process, ^heater, reason} -> exit(reason)
end
end
defp get_metrics() do
case System.cmd("powermetrics", ["--samplers", "smc", "-i1", "-n1"]) do
{output, 0} -> parse_metrics(output)
{_, n} -> exit(n)
end
end
defp parse_metrics(data) do
data
|> String.split("\n", trim: true)
|> Enum.map(fn
"CPU die temperature: " <> temp ->
IO.puts("Current temperature: #{temp}")
case Float.parse(temp) do
{temp, " C"} -> {temp, false}
{temp, " C (fan)"} -> {temp, true}
end
_line ->
nil
end)
|> Enum.filter(&(&1 != nil))
|> hd()
end
end
degrees =
case System.argv() do
[] ->
100
[arg | _] ->
case Integer.parse(arg) do
{deg, ""} ->
deg
_ ->
IO.puts("Could not parse '#{arg}'")
System.stop()
end
end
IO.puts("running for #{degrees}°C")
Control.run(degrees)
|> IO.inspect(label: "exit code")
|> System.stop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment