Elixir Tracing Framework
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
lib (fix) fixed flame graph not aggregating all frames Sep 12, 2017
scripts (feat) added flame graphs Sep 7, 2017
test (fix) disabled doctest in tracer_test.exs Sep 7, 2017
LICENSE.md Added LICENSE.md file Sep 4, 2017
mix.exs added ex_doc to mix file Sep 4, 2017
mix.lock added ex_doc to mix file Sep 4, 2017


Tracer - Elixir Tracing Framework

Build Status

Tracer is a tracing framework for elixir which features an easy to use high level interface, extensibility and safety for using in production.


If you need to integrate Tracer to your project, then you can install it from Hex, by adding tracer to your list of dependencies in mix.exs:

def deps do
  [{:tracer, "~> 0.1.1"}]

To use Tracer from the cli, then download it directly from GitHub.

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.

$ git clone git@github.com:gabiz/tracer.git
$ cd tracer

$ mix deps.get
$ iex --name tracer@ -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@> use Tracer
iex(tracer@> run Count, node: :"phoenix@", ...


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

iex(2)> run Count, process: self(), match: global String.split(string, pattern)
started tracing
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

iex(1)> run Duration, match: global Map.new(param)
started tracing
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
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

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
iex(2)> Map.drop(%{a: 1, b: 2, c: 3}, [:a, :b])
%{c: 3}                                
iex(3)> stop
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

iex(17)> run FlameGraph, node: :"phoenix@", process: SampleApp.Endpoint,
        max_message_count: 10000, max_queue_size: 10000, file_name: "phoenix.svg",
        ignore: "sleep", resolution: 10, max_depth: 100
started tracing
iex(18)> stop
done tracing: :stop_command

Click here (not image) for interactive SVG Flame Graph

Flame Graph

Building your own Tool

Tools have a similar structure like GenServers.

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])

  # Called when the tool run starts
  def handle_start(event, state) do

  # 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)

  # Called when the tool run completes
  def handle_end(event, state) do