EZProfiler.Manager (ezprofiler_deps v1.1.0)

This module requires the ezprofiler escript, see...

https://github.com/nhpip/ezprofiler.git

https://hex.pm/packages/ezprofiler

https://hexdocs.pm/ezprofiler/api-reference.html

This module provides the ability to perform code profiling programmatically within an application rather than via the ezprofiler CLI. This maybe useful in environments where shell access maybe limited. Instead the output can be redirected to a logging subsystem for example.

Use of this module still requires the ezprofiler escript, but it will be automatically initialized in the background.

ezprofiler can be downloaded from https://github.com/nhpip/ezprofiler or added to deps in mix.exs along with this package:

defp deps do
  [
    {:ezprofiler, git: "https://github.com/nhpip/ezprofiler.git"},
    {:ezprofiler_deps, git: "https://github.com/nhpip/ezprofiler_deps.git"}
  ]
end

This profiling mechanism supports two modes of operation, blocking and non-blocking

In blocking mode the user starts profiling and then calls a blocking function to wait for the results.

In non-blocking mode the results are sent as a message, this will be a handle_info/2 in the case of a GenServer

The results are a map or list of maps that contains:

  %{type: type,            # One of :normal or :pseudo
    label: label,          # The label used by this run
    filename: filename,    # The results file
    profiler: profiler,    # "eprof, "fprof", "cprof" or :no_profiler in case of pseudo
    results_data: results  # String containing the results
  }

blocking-example

Blocking Example

  EZProfiler.Manager.start_ezprofiler(%EZProfiler.Manager.Configure{ezprofiler_path: :deps})
  ...
  ...
  with :ok <- EZProfiler.Manager.set_mgmt_mode_blocking()  ## Set before `enable_profiling/0`
       :ok <- EZProfiler.Manager.enable_profiling(),
       {:ok, run_results} <- EZProfiler.Manager.wait_for_results(),
       {:ok, results} <- EZProfiler.Manager.get_profiling_results(true)
  do
      {:ok, results}
  else
    rsp ->
      rsp
  end
  ...
  ...
  EZProfiler.Manager.stop_ezprofiler()

The function wait_for_results/1 will return the results of that current profiling run, whereas get_profiling_results/1 returns a list of results for that entire run (e.g. if label transition is selected). This is cleared if enable_profiling/1 or disable_profiling/0 is called

non-blocking-example-as-a-genserver

Non-blocking Example as a GenServer

  ## Your handle_cast
  def handle_cast(:start_profiling, state) do
    EZProfiler.Manager.start_ezprofiler(%EZProfiler.Manager.Configure{ezprofiler_path: :deps})
    EZProfiler.Manager.set_mgmt_mode_non_blocking()  ## Set before `enable_profiling/0`
    EZProfiler.Manager.enable_profiling()
    {:noreply, state}
  end

  def handle_info({:ezprofiler_results, results}, state) do
    EZProfiler.Manager.stop_ezprofiler()  # Don't have to stop if you don't want to
    do_something_with_results(results.type, results.filename, results.results_data)
    {:noreply, state}
  end

  ## Change timeout with `EZProfiler.Manager.profiling_time/1`
  def handle_info({:ezprofiler, :profiling_timeout}, state) do
    # Ooops, but it's possible results are available
    {:ok, results} = EZProfiler.Manager.get_profiling_results()
    {:noreply, state}
  end

  ## Change timeout with `EZProfiler.Manager.profiling_start_wait/1`
  def handle_info({:ezprofiler, :start_profiling_timeout}, state) do
    # Ooops
    {:noreply, state}
  end

labels

Labels

When using EZProfiler.Manager.enable_profiling/1 either a single label or a list of labels can be specified. In the case of a list there are two modes of operation, label transition (labeltran) true or label transition false (the default). The behavior is as follows:

Label Transition false

This effectively a request to profile one-of those labels. The first matching label is selected for profiling and the rest of the labels are ignored.

Label Transition true

In this case all specified labels shall be profiled sequentially (order doesn't matter), effectively the profiler automatically re-enables profiling after a label match. A label that matches and is profiled, will removed from the list of labels to be profiled next and profiling is re-enabled for the remaining labels. This allows profiling to follow the flow of code through your application, even if processes are switched.

It is important to note that the rule of only one process at a time can be profiled still exists, so ideally profiled code calls should be sequential. However, if there are sections of code that need to be profiled that overlap in time ezprofiler performs pseudo profiling. This is where ezprofiler will calculate and display how long the profiled code took to execute.

Use allow_label_transition/1 to configure label transition.

Link to this section Summary

Functions

If many labels are specified in enable_profiling/1 setting this to true will automatically re-enable profiling after one label has been profiled.

Disables code profiling. The equivalent of hitting r in the CLI.

Enables code profiling. The equivalent of hitting c or c label in the CLI. A single label, or list of labels can be specified

Returns the resulting code profiling results. If the option display is set to true it will also output the stdout.

Specifies how long we wait for profiling to actually start once enable_profiling/1 is issued.

Specifies how long we wait for profiling to actually complete once started.

Sets the operating mode to blocking. This requires the user to make calls to EZProfiler.Manager.wait_for_results/0 and EZProfiler.Manager.get_profiling_results/0 to get the profiling results.

Sets the operating mode to non-blocking. This will cause events from the profiler to be sent to the process id specified. If no process id is specified the result is sent to self()

Starts and configures the ezprofiler escript. Takes the %EZProfiler.Manager.Configure{} struct as configuration.

Stops the ezprofiler escript. The equivalent of hitting q in the CLI.

Waits timeout milliseconds (default 5000) for any results and returns the result as a map.

Link to this section Types

@type display() :: boolean()
@type filename() :: String.t()
@type label() :: atom() | String.t() | list()
@type labels() :: [label()]
Link to this type

profile_data()

@type profile_data() :: String.t()
Link to this type

profiling_cfg()

@type profiling_cfg() :: EZProfiler.Manager.Configure.t()
Link to this type

profiling_time()

@type profiling_time() :: integer()
@type result() :: map()
@type results() :: [map()]
@type self() :: pid()
@type wait_time() :: integer()

Link to this section Functions

Link to this function

allow_label_transition(allow?)

@spec allow_label_transition(boolean()) :: :ok | {:error, :not_running}

If many labels are specified in enable_profiling/1 setting this to true will automatically re-enable profiling after one label has been profiled.

example

Example

  EZProfiler.Manager.enable_profiling(["L1", "L2", "L3"])

L1 is hit and profiled the next profile will be the equivalent of issuing:

  EZProfiler.Manager.enable_profiling(["L2", "L3"])

Then L2 hit:

  EZProfiler.Manager.enable_profiling(["L3"])

This permits profiling a flow that may involve messages between a number of processes.

Link to this function

disable_profiling()

@spec disable_profiling() :: :ok

Disables code profiling. The equivalent of hitting r in the CLI.

Link to this function

enable_profiling(label \\ :any_label)

@spec enable_profiling(label() | labels() | none()) :: :ok

Enables code profiling. The equivalent of hitting c or c label in the CLI. A single label, or list of labels can be specified

Link to this function

get_profiling_results(display \\ false)

@spec get_profiling_results(display() | false) ::
  {:ok, results()} | {:error, :not_running} | {:error, atom()}

Returns the resulting code profiling results. If the option display is set to true it will also output the stdout.

On success it will return the tuple {:ok, results}

Link to this function

profiling_start_wait(time)

@spec profiling_start_wait(profiling_time() | :infinity) ::
  :ok | {:error, :not_running}

Specifies how long we wait for profiling to actually start once enable_profiling/1 is issued.

Time is specified in milliseconds or the atom :infinity, default :infinity

Link to this function

profiling_time(time)

@spec profiling_time(profiling_time()) :: :ok | {:error, :not_running}

Specifies how long we wait for profiling to actually complete once started.

Time is specified in milliseconds.

Link to this function

set_mgmt_mode_blocking()

@spec set_mgmt_mode_blocking() :: :ok | {:error, :not_running}

Sets the operating mode to blocking. This requires the user to make calls to EZProfiler.Manager.wait_for_results/0 and EZProfiler.Manager.get_profiling_results/0 to get the profiling results.

Link to this function

set_mgmt_mode_non_blocking(pid \\ nil)

@spec set_mgmt_mode_non_blocking(pid() | self()) :: :ok | {:error, :not_running}

Sets the operating mode to non-blocking. This will cause events from the profiler to be sent to the process id specified. If no process id is specified the result is sent to self()

Three messages can be received:

{:ezprofiler_results, result}
{:ezprofiler, :profiling_timeout}
{:ezprofiler, :start_profiling_timeout}

Result is a map:

    %{
        type: type,          # One of :normal or :pseudo
        label: label,          # The label used by this run
        filename: filename,    # The results file
        profiler: profiler,    # "eprof, "fprof", "cprof" or :no_profiler in case of pseudo
        results_data: results  # String containing the results
    }

In the case of a GenServer these will be received by handle_info/2

If {:error, :profiling_timeout} is returned results may still be available, call EZProfiler.Manager.get_profiling_results/0 to retrieve them.

Link to this function

start_ezprofiler(profiling_cfg \\ %Configure{})

@spec start_ezprofiler(profiling_cfg()) ::
  {:ok, :started}
  | {:error, :timeout}
  | {:error, :not_started}
  | {:error, :already_running}

Starts and configures the ezprofiler escript. Takes the %EZProfiler.Manager.Configure{} struct as configuration.

Most fields map directly onto the equivalent arguments for starting ezprofiler.

The exception to this is ezprofiler_path that takes the following options:

  :system - if `ezprofiler` is defined via the `PATH` env variable.
  :deps - if `ezprofiler` is included as a dependency in `mix.exs`
  path - a string specifying the full path for `ezprofiler`

example

Example

  %EZProfiler.Manager.Configure{
    cookie: nil,
    cpfo: "false",
    directory: "/tmp/",
    ezprofiler_path: :system,
    mf: "_:_",
    node: nil,
    profiler: "eprof",
    labeltran: true,
    sort: "mfa"
  }
Link to this function

stop_ezprofiler()

@spec stop_ezprofiler() ::
  {:ok, :stopped} | {:error, :not_stopped} | {:error, :not_running}

Stops the ezprofiler escript. The equivalent of hitting q in the CLI.

Link to this function

wait_for_results(wait_time \\ 5000)

@spec wait_for_results(wait_time() | 5000) ::
  {:ok, result()}
  | {:error, :timeout}
  | {:error, :profiling_timeout}
  | {:error, :start_profiling_timeout}
  | {:error, :not_running}

Waits timeout milliseconds (default 5000) for any results and returns the result as a map.

  %{type: type,            # One of :normal or :pseudo
    label: label,          # The label used by this run
    filename: filename,    # The results file
    profiler: profiler,    # "eprof, "fprof", "cprof" or :no_profiler in case of pseudo
    results_data: results  # String containing the results
  }

If {:error, :profiling_timeout} is returned results may still be available, call EZProfiler.Manager.get_profiling_results() to retrieve them

Link to this function

wait_for_results_non_block(pid \\ nil)

This function is deprecated. Use EZProfiler.Manager.set_mgmt_mode_non_blocking/1 instead.
@spec wait_for_results_non_block(pid() | self()) :: :ok | {:error, :not_running}