Packages
grizzly
8.6.9
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/trace.ex
defmodule Grizzly.Trace do
@moduledoc """
Module that tracks the commands that are sent and received by Grizzly
The trace will hold in memory the last 300 messages. If you want to generate
a log file of the trace records you use `Grizzly.Trace.dump/1`.
The log format is:
```
timestamp source destination sequence_number command_name command_parameters
```
If you want to list the records that are currently being held in memory you
can use `Grizzly.Trace.list/0`.
If you want to start traces from a fresh start you can call
`Grizzly.Trace.clear/0`.
"""
use GenServer
alias Grizzly.Trace.Record
@type trace_opt :: {:name, atom()} | {:size, pos_integer()} | {:record_keepalives, boolean()}
@type src() :: Grizzly.node_id() | :grizzly
@type dest() :: Grizzly.node_id() | :grizzly
@type log_opt() :: {:src, src()} | {:dest, dest()}
@type format() :: :text | :raw
@type list_opt() :: {:node_id, Grizzly.node_id()}
@default_size 300
@default_format :text
@doc "Start the trace server."
@spec start_link([trace_opt()]) :: GenServer.on_start()
def start_link(opts \\ []) do
{name, opts} = Keyword.pop(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@doc """
Serialize a list of trace records into a binary. The format can be one of:
* `:text` - Each record is on a new line and is
formatted as `timestamp source destination sequence_number command_name
command_binary`. This is the default format.
* `:raw` - Each record is on a new line and is formatted as
`timestamp source -> destination: binary`.
"""
@spec format([Record.t()], format()) :: binary()
def format(records, format \\ @default_format)
def format(records, fmt) when fmt in [:text, :raw],
do: Enum.map_join(records, "\n", &Record.to_string(&1, fmt))
@doc "Dump trace records into a file using Erlang External Term Format."
@spec dump(binary()) :: :ok | {:error, atom()}
def dump(file) do
file_contents = :erlang.term_to_binary(list(), compressed: 9)
File.write(file, file_contents)
end
@doc """
Write trace records to standard out. See `format/2` for the available formats.
"""
@spec print(list(Record.t()) | format(), [list_opt()]) :: :ok
def print(records_or_format, opts)
def print(records, _opts) when is_list(records), do: records |> format() |> IO.puts()
def print(fmt, opts) when is_atom(fmt), do: opts |> list() |> format(fmt) |> IO.puts()
def print(fmt_or_opts)
def print(fmt) when is_atom(fmt), do: print(fmt, [])
def print(opts) when is_list(opts), do: print(@default_format, opts)
@spec print() :: :ok
def print(), do: print([])
@doc "Add a record to the trace buffer."
@spec log(GenServer.server(), binary(), [log_opt()]) :: :ok
def log(name, binary, opts) do
GenServer.cast(name, {:log, binary, opts})
end
@doc "See `log/3`"
@spec log(binary(), [log_opt()]) :: :ok
def log(binary, opts \\ []) when is_binary(binary), do: log(__MODULE__, binary, opts)
@doc "Reset the trace buffer."
@spec clear(GenServer.server()) :: :ok
def clear(name \\ __MODULE__), do: GenServer.call(name, :clear)
@doc "List all records in the trace buffer."
@spec list(GenServer.server(), [list_opt()]) :: [Record.t()]
def list(name, opts) do
node_id_filter = Keyword.get(opts, :node_id, nil)
records = GenServer.call(name, :list)
if node_id_filter do
Enum.filter(records, &(&1.src == node_id_filter || &1.dest == node_id_filter))
else
records
end
end
@doc "See `list/2`."
@spec list(GenServer.server() | [list_opt()]) :: [Record.t()]
def list(opts) when is_list(opts), do: list(__MODULE__, opts)
def list(name), do: list(name, [])
@doc "See `list/2`."
@spec list() :: [Record.t()]
def list(), do: list(__MODULE__, [])
@doc "Change the max size of the trace buffer from the default of #{@default_size}."
@spec resize(GenServer.server(), pos_integer()) :: :ok
def resize(name \\ __MODULE__, size), do: GenServer.call(name, {:resize, size})
@doc "Enable or disable logging of keepalive frames."
@spec record_keepalives(GenServer.server(), boolean()) :: :ok
def record_keepalives(name, enabled?), do: GenServer.call(name, {:record_keepalives, enabled?})
@doc "See `record_keepalives/2`."
@spec record_keepalives(boolean()) :: :ok
def record_keepalives(enabled? \\ true), do: record_keepalives(__MODULE__, enabled?)
@impl GenServer
def init(opts) do
size = Keyword.get(opts, :size, @default_size)
record_keepalives = Keyword.get(opts, :record_keepalives, true)
{:ok, %{buffer: CircularBuffer.new(size), record_keepalives: record_keepalives}}
end
@impl GenServer
def handle_cast({:log, <<0x23, 0x03, _ack_flag>>, _opts}, %{record_keepalives: false} = state) do
{:noreply, state}
end
def handle_cast({:log, binary, opts}, state) do
record = Record.new(binary, opts)
{:noreply, %{state | buffer: CircularBuffer.insert(state.buffer, record)}}
end
@impl GenServer
def handle_call(:clear, _from, %{buffer: %CircularBuffer{max_size: size}} = state) do
{:reply, :ok, %{state | buffer: CircularBuffer.new(size)}}
end
def handle_call(:list, _from, %{buffer: buffer} = state) do
{:reply, CircularBuffer.to_list(buffer), state}
end
def handle_call({:record_keepalives, enabled?}, _from, state) do
{:reply, :ok, %{state | record_keepalives: enabled?}}
end
def handle_call({:resize, size}, _from, %{buffer: buffer} = state) do
new_buffer =
buffer
|> CircularBuffer.to_list()
|> Enum.reduce(CircularBuffer.new(size), fn record, buffer ->
CircularBuffer.insert(buffer, record)
end)
{:reply, :ok, %{state | buffer: new_buffer}}
end
end