Last active
January 21, 2021 09:52
-
-
Save lud/f714bf300d2342ba52169b8e5dcf5baf to your computer and use it in GitHub Desktop.
This file contains hidden or 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
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