# Tracer - Elixir Tracing Framework
[![Build Status](https://api.travis-ci.org/gabiz/tracer.svg)](https://travis-ci.org/gabiz/tracer)
**Tracer** is a tracing framework for elixir which features an easy to use high level interface, extensibility and safety for using in production.
## Installation
If you need to integrate **Tracer** to your project, then you can install it from
[Hex](https://hex.pm/packages/tracer), by adding `tracer` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:tracer, "~> 0.1.1"}]
end
```
To use Tracer from the cli, then download it directly from [GitHub](https://github.com/gabiz/tracer).
When firing `iex` you might want to specify the node name so that you can trace other nodes remotely. Then enter the `use Tracer` command to be able to use its functions as commands without the `Tracer` prefix.
```elixir
$ git clone git@github.com:gabiz/tracer.git
...
$ cd tracer
$ mix deps.get
...
$ iex --name tracer@127.0.0.1 -S mix
Erlang/OTP 19 [erts-8.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.5.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(tracer@127.0.0.1)1> use Tracer
:ok
iex(tracer@127.0.0.1)2>
nil
iex(tracer@127.0.0.1)3> run Count, node: :"phoenix@127.0.0.1", ...
```
## Tools
Tools are tracing components that focus on a specific tracing aspect. They are implemented as Elixir modules so you can create your own tools.
Tracer currently provides the following tools:
* The `Count` tool counts events.
* The `Duration` tool measures how long it takes to execute a function.
* The `CallSeq` - 'Call Sequence' tool displays function call sequences.
* The `FlameGraph` tool which aggregates stack frames over a flame graph.
* The `Display` tool displays standard tracing events.
## Count Tool Example
```elixir
iex(2)> run Count, process: self(), match: global String.split(string, pattern)
started tracing
:ok
iex(3)>
nil
iex(4)> String.split("Hello World", " ")
["Hello", "World"]
iex(5)> String.split("Hello World", " ")
["Hello", "World"]
iex(6)> String.split("Hello World", "o")
["Hell", " W", "rld"]
iex(7)> String.split("Hello", "o")
["Hell", ""]
iex(8)> done tracing: tracing_timeout 30000
1 [string:"Hello World", pattern:"o"]
1 [string:"Hello" , pattern:"o" ]
2 [string:"Hello World", pattern:" "]
```
## Duration Tool Example
```elixir
iex(1)> run Duration, match: global Map.new(param)
started tracing
:ok
iex(2)> Map.new(%{a: 1})
4 '#PID<0.151.0>' Map.new/1 [param: %{a: 1}]
%{a: 1}
iex(3)> Map.new(%{b: 2})
3 '#PID<0.151.0>' Map.new/1 [param: %{b: 2}]
%{b: 2}
iex(4)> Map.new(%{c: [1, 2,3]})
6 '#PID<0.151.0>' Map.new/1 [param: %{c: [1, 2, 3]}]
%{c: [1, 2, 3]}
iex(5)> stop
:ok
done tracing: :stop_command
```
Use `aggregation` option to collect all the duration samples and return you a combined result.
`aggregation:` option can be one of `:sum`, `:avg`, `:min`, `:max`, `:dist`
## Call Sequence Tool Example
```elixir
iex(1)> run CallSeq, show_args: true, show_return: true, start_match: &Map.drop/2,
max_message_count: 10000, max_queue_size: 10000
started tracing
:ok
iex(2)> Map.drop(%{a: 1, b: 2, c: 3}, [:a, :b])
%{c: 3}
iex(3)> stop
:ok
done tracing: :stop_command
-> Map.drop/2 [[%{a: 1, b: 2, c: 3}, [:a, :b]]]
-> Enum.to_list/1 [[[:a, :b]]]
<- Enum.to_list/1 [:a, :b]
-> Map.drop_list/2 [[[:a, :b], %{a: 1, b: 2, c: 3}]]
-> :maps.remove/2 [[:a, %{a: 1, b: 2, c: 3}]]
<- :maps.remove/2 %{b: 2, c: 3}
-> Map.drop_list/2 [[[:b], %{b: 2, c: 3}]]
-> :maps.remove/2 [[:b, %{b: 2, c: 3}]]
<- :maps.remove/2 %{c: 3}
-> Map.drop_list/2 [[[], %{c: 3}]]
<- Map.drop_list/2 %{c: 3}
<- Map.drop/2 %{c: 3}
-> :erl_eval.ret_expr/3 [[%{c: 3}, [], :none]]
<- :erl_eval.ret_expr/3 {:value, %{c: 3}, []}
<- :erl_eval.do_apply/6 {:value, %{c: 3}, []}
<- :erl_eval.expr/5 {:value, %{c: 3}, []}
```
## Flame Graph Tool Example
```elixir
iex(17)> run FlameGraph, node: :"phoenix@127.0.0.1", process: SampleApp.Endpoint,
max_message_count: 10000, max_queue_size: 10000, file_name: "phoenix.svg",
ignore: "sleep", resolution: 10, max_depth: 100
started tracing
:ok
iex(18)> stop
:ok
done tracing: :stop_command
```
[Click here (not image) for interactive SVG Flame Graph](https://s3.amazonaws.com/gapix/flame_graph.svg)
![FlameGraph](https://s3.amazonaws.com/gapix/flame_graph.svg?sanitize=true)
## Building your own Tool
Tools have a similar structure like GenServers.
```elixir
defmodule MyTool do
alias __MODULE__
alias Tracer.Probe
use Tracer.Tool
# store your tool's state
defstruct []
def init(opts) do
# init_tool initializes the tool
init_state = init_tool(%MyTool{}, opts, [:match])
case Keyword.get(opts, :match) do
nil -> init_state
matcher ->
type = Keyword.get(opts, :type, :call)
probe = Probe.new(type: type,
process: get_process,
match: matcher)
set_probes(init_state, [probe])
end
end
# Called when the tool run starts
def handle_start(event, state) do
state
end
# Called when a trace event triggers
def handle_event(event, state) do
# report event will call to_string(event) to format
# your event, so you can create your own events
report_event(state, event)
state
end
# Called when the tool run completes
def handle_end(event, state) do
state
end
end
```