-
-
Notifications
You must be signed in to change notification settings - Fork 23
/
heart.ex
128 lines (111 loc) · 4.53 KB
/
heart.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
defmodule Nerves.Runtime.Heart do
@moduledoc """
Functions for querying Nerves Heart and the device's watchdog
Nerves Heart integrates Erlang's
[heart](https://www.erlang.org/doc/man/heart.html) process with a hardware
watchdog. This makes it possible for a device to recover from a hang. The
way it works is that the Erlang runtime regularly checks that it's ok. If so,
it sends a message to `heart`. Nerves heart then pets the hardware watchdog.
If messages ever stop being sent to `heart`, the hardware watchdog will trip
and reboot the device. You can add additional health checks for your
application by providing a callback to `:heart.set_callback/2`.
"""
@typedoc """
Nerves Heart's current status
* `:program_name` - `"nerves_heart"`
* `:program_version` - Nerves heart's version number
* `:identity` - The hardware watchdog that's being used
* `:firmware_version` - An integer that represents the hardware watchdog's firmware revision
* `:options` - Hardware watchdog options as reported by Linux
* `:time_left` - How many seconds are left before the hardware watchdog triggers a reboot
* `:pre_timeout` - How many seconds before the watchdog expires that Linux
will receive a pre-timeout notification
* `:timeout` - The hardware watchdog
timeout. This is only changeable in the Linux configuration
* `:last_boot` - What caused the most recent boot. Whether this is reliable
depends on the watchdog.
* `:heartbeat_timeout` - Erlang's heartbeat timeout setting. Note that the
hardware watchdog timeout supersedes this since it reboots.
"""
@type info() :: %{
program_name: String.t(),
program_version: Version.t(),
identity: String.t(),
firmware_version: non_neg_integer(),
options: non_neg_integer() | [atom()],
time_left: non_neg_integer(),
pre_timeout: non_neg_integer(),
timeout: non_neg_integer(),
last_boot: :power_on | :watchdog,
heartbeat_timeout: non_neg_integer()
}
@doc """
Return whether Nerves heart is running
If you're using a Nerves device, this always returns `true` except possibly
when porting Nerves to new hardware. It is a quick sanity check.
"""
@spec running?() :: boolean()
def running?() do
case status() do
{:ok, %{program_name: "nerves_heart"}} -> true
_ -> false
end
end
@doc """
Return the current Nerves Heart status
Errors are returned when not running Nerves Heart
"""
@spec status() :: {:ok, info()} | :error
def status() do
{:ok, cmd} = :heart.get_cmd()
parse_cmd(cmd)
rescue
ArgumentError ->
# When heart isn't running, an ArgumentError is raised
:error
end
@doc """
Raising version of status/0
"""
@spec status!() :: info()
def status!() do
{:ok, results} = status()
results
end
@doc false
@spec parse_cmd(list()) :: {:ok, info()} | :error
def parse_cmd([]), do: :error
def parse_cmd(cmd) when is_list(cmd) do
result =
cmd
|> to_string()
|> String.split("\n")
|> Enum.map(&String.split(&1, "=", parts: 2))
|> Enum.map(&parse_attribute/1)
|> Enum.reject(&is_nil/1)
|> Map.new()
{:ok, result}
rescue
_ -> :error
end
defp parse_attribute(["program_name", str]), do: {:program_name, str}
defp parse_attribute(["program_version", str]), do: {:program_version, Version.parse!(str)}
defp parse_attribute(["identity", str]), do: {:identity, str}
defp parse_attribute(["firmware_version", str]), do: {:firmware_version, String.to_integer(str)}
defp parse_attribute(["options", "0x" <> hex]), do: {:options, String.to_integer(hex, 16)}
defp parse_attribute(["options", option_list]), do: {:options, parse_option_list(option_list)}
defp parse_attribute(["time_left", str]), do: {:time_left, String.to_integer(str)}
defp parse_attribute(["pre_timeout", str]), do: {:pre_timeout, String.to_integer(str)}
defp parse_attribute(["timeout", str]), do: {:timeout, String.to_integer(str)}
defp parse_attribute(["last_boot", str]), do: {:last_boot, parse_last_boot(str)}
defp parse_attribute(["heartbeat_timeout", str]),
do: {:heartbeat_timeout, String.to_integer(str)}
defp parse_attribute([_unknown, _str]), do: nil
defp parse_attribute([""]), do: nil
defp parse_last_boot("power_on"), do: :power_on
defp parse_last_boot("watchdog"), do: :watchdog
defp parse_last_boot(other), do: {:unknown, other}
defp parse_option_list(options) do
for s <- String.split(options, ","), s != "", do: String.to_atom(s)
end
end