[
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: tests\n\non: push\n\njobs:\n  tests:\n    name: Run Tests\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        otp: ['19.3']\n        elixir: ['1.5', '1.7']\n    env:\n      MIX_ENV: test\n      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-elixir@v1\n        with:\n         otp-version: ${{ matrix.otp }}\n         elixir-version: ${{ matrix.elixir }}\n      - uses: actions/cache@v1\n        with:\n         path: deps\n         key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\n         restore-keys: |\n           ${{ runner.os }}-mix-\n      - run: mix deps.get\n      - run: mix test --seed 0 # disable randomization\n"
  },
  {
    "path": ".gitignore",
    "content": "/ebin\n/deps\nerl_crash.dump\n*.ez\ntmp_exprof\n/_build\n/doc"
  },
  {
    "path": "CHANGELOG.md",
    "content": "0.2.4\n------\n#### Changes\n* Makes sure it is OK for the profiled code to send/receive messages (#13).\n* Fix for warnings with elixir v1.11.\n  * Add :tools to extra_applications in mix.exs (#15).\n\n0.2.3\n------\n#### Changes\n* Fixes issue on windows where you get File.Error permission denied.\n   - Changed to use randomized file name (#11).\n\n0.2.2\n------\n#### Changes\n* Return block result after completion (#9).\n* Link profiled process to avoid hang (#10).\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013-2015 parroty\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "ExProf\n============\n\n[![Build Status](https://github.com/parroty/excoveralls/workflows/tests/badge.svg)](https://github.com/parroty/exprof/actions)\n[![hex.pm version](https://img.shields.io/hexpm/v/exprof.svg)](https://hex.pm/packages/exprof)\n[![hex.pm downloads](https://img.shields.io/hexpm/dt/exprof.svg)](https://hex.pm/packages/exprof)\n\nA simple code profiler for Elixir using eprof.\n\nIt provides a simple macro as a wrapper for Erlang's <a href=\"http://www.erlang.org/doc/man/eprof.html\" target=\"_blank\">:eprof</a> profiler.\n\n### Install\nAdd `:exprof` to `deps` section of `mix.exs`.\n\n```elixir\n  def deps do\n    [ {:exprof, \"~> 0.2.0\"} ]\n  end\n```\n\n### Usage\nimport \"ExProf.Macro\", then use \"profile\" macro to start profiling. It\nprints out results, and returns them as list of records, along with the\nresult of the profiled block.\n\n```elixir\ndefmodule SampleRunner do\n  import ExProf.Macro\n\n  @doc \"analyze with profile macro\"\n  def do_analyze do\n    profile do\n      :timer.sleep 2000\n      IO.puts \"message\\n\"\n    end\n  end\n\n  @doc \"get analysis records and sum them up\"\n  def run do\n    {records, _block_result} = do_analyze\n    total_percent = Enum.reduce(records, 0.0, &(&1.percent + &2))\n    IO.inspect \"total = #{total_percent}\"\n  end\nend\n```\n\n### Run\nAn example to use in iex console.\n\n```elixir\n$ iex -S mix\n..\niex(1)> SampleRunner.run\nmessage\n\nFUNCTION                                 CALLS      %  TIME  [uS / CALLS]\n--------                                 -----    ---  ----  [----------]\n'Elixir.IO':puts/2                           1   0.86     1  [      1.00]\nio:o_request/3                               1   1.72     2  [      2.00]\nio:put_chars/2                               1   1.72     2  [      2.00]\nerlang:group_leader/0                        1   1.72     2  [      2.00]\nio:request/2                                 1   2.59     3  [      3.00]\nio:execute_request/2                         1   2.59     3  [      3.00]\n'Elixir.SampleRunner':'-run/0-fun-0-'/0      1   2.59     3  [      3.00]\n'Elixir.IO':map_dev/1                        1   3.45     4  [      4.00]\nerlang:demonitor/2                           1   4.31     5  [      5.00]\nio:io_request/2                              1   6.03     7  [      7.00]\nio:wait_io_mon_reply/2                       1   6.90     8  [      8.00]\n'Elixir.IO':puts/1                           1   8.62    10  [     10.00]\nunicode:characters_to_binary/2               1  11.21    13  [     13.00]\ntimer:sleep/1                                1  14.66    17  [     17.00]\nerlang:monitor/2                             1  31.03    36  [     36.00]\n\"total = 100.0\"\n```\n\n### Add a Mix Task\nAn example to use as mix tasks.\n\n```elixir\ndefmodule Mix.Tasks.Exprof do\n  @shortdoc \"Profile using ExProf\"\n  use Mix.Task\n  import ExProf.Macro\n\n  def run(_mix_args) do\n    profile do: do_work(2)\n  end\n\n  defp do_work(n) do\n    :timer.sleep(n * 1000)\n  end\nend\n```\n"
  },
  {
    "path": "lib/exprof/analyzer.ex",
    "content": "defmodule ExProf.Analyzer do\n  @moduledoc \"\"\"\n  Proivdes helper methods for operating Prof records.\n  \"\"\"\n  import ExPrintf\n\n  @doc \"\"\"\n  Returns records only have major percent values, and filter out rest of them.\n  The default is to pick \"80%\" and it can be changed by specifying rate argument.\n  \"\"\"\n  def get_top_percent_items(records, rate \\\\ 80.0) do\n    sorted_records = Enum.sort(records, &(&1.percent > &2.percent))\n    do_get_top_percent_items(sorted_records, rate, [])\n  end\n\n  defp do_get_top_percent_items(records, rate, acc) when length(records) == 0 or rate <= 0.0 do\n    Enum.reverse(acc)\n  end\n\n  defp do_get_top_percent_items([head|tail], rate, acc) do\n    do_get_top_percent_items(tail, rate - head.percent, [head|acc])\n  end\n\n  @doc \"\"\"\n  Print records to screen\n  \"\"\"\n  def print(records) do\n    print_header()\n    Enum.each(records, &(do_print(&1)))\n  end\n\n  defp print_header do\n    IO.puts \"FUNCTION                                           CALLS       %  TIME  [uS / CALLS]\"\n    IO.puts \"--------                                           -----     ---  ----  [----------]\"\n  end\n\n  defp do_print(record) do\n    printf(\"%-50s %-6d %6.2f %5d  [%10.2f]\\n\",\n      [String.slice(record.function, 0, 50), record.calls, record.percent, record.time, record.us_per_call])\n  end\nend\n"
  },
  {
    "path": "lib/exprof/macro.ex",
    "content": "defmodule ExProf.Macro do\n  @moduledoc \"\"\"\n  Provides a macro to profile a block of code.\n  \"\"\"\n\n  @doc \"\"\"\n  A macro to specify the code block to profile.\n\n  It spawns a new process to execute the code block for isolating the profile result.\n  (ex.)\n\n      profile do\n        :timer.sleep 2000\n      end\n\n  \"\"\"\n  defmacro profile(do: code) do\n    quote do\n      ref = make_ref()\n      pid = spawn_link(ExProf.Macro, :execute_profile, [fn -> unquote(code) end, ref])\n      ExProf.start(pid)\n      send pid, {ref, self()}\n\n      result =\n        receive do\n          {^ref, result} -> result\n        end\n\n      ExProf.stop\n      records = ExProf.analyze\n\n      {records, result}\n    end\n  end\n\n  @doc \"\"\"\n  An internal method for initiating profiling.\n  \"\"\"\n  def execute_profile(func, ref) do\n    receive do\n      {^ref, sender} ->\n        send sender, {ref, func.()}\n        forward_other_messages(sender)\n    end\n  end\n\n  defp forward_other_messages(sender) do\n    receive do\n      message ->\n        send sender, message\n        forward_other_messages(sender)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/exprof/reader.ex",
    "content": "defmodule ExProf.Reader do\n  @moduledoc \"\"\"\n  A reader for eprof outputs.\n  \"\"\"\n\n  @doc \"\"\"\n  Read the eprof output file and returns the parsed result of Prof records.\n  \"\"\"\n  def read(file_name) do\n    File.read!(file_name) |> String.split(\"\\n\") |> parse([])\n  end\n\n  defp parse([], acc), do: Enum.reverse(acc)\n  defp parse([head|tail], acc) do\n    case parse_record(head) do\n      nil    -> parse(tail, acc)\n      record -> parse(tail, [record|acc])\n    end\n  end\n\n  defp parse_record(head) do\n    case Regex.run(~r/(.+?\\/[0-9])\\s+(.+?)\\s+(.+?)\\s+(.+?)\\s+\\[\\s+(.+?)\\]/, head) do\n      [_all, function, calls, percent, time, us_per_call] ->\n        %Prof{function: function, calls: String.to_integer(calls), percent: String.to_float(percent),\n                 time: String.to_integer(time), us_per_call: String.to_float(us_per_call)}\n      nil -> nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/exprof/records.ex",
    "content": "defmodule Prof do\n  defstruct function: nil, calls: nil, percent: nil, time: nil, us_per_call: nil\nend\n"
  },
  {
    "path": "lib/exprof.ex",
    "content": "defmodule ExProf do\n  @moduledoc \"\"\"\n  Wrapper for eprof library.\n  It needs to be called in start -> stop -> analyze order.\n  \"\"\"\n\n  # temporary file for storing eprof output\n  @tmp_prof_name 'tmp_exprof'\n\n  @doc \"\"\"\n  Start the profiling for the specified pid.\n  \"\"\"\n  def start(pid \\\\ self()) do\n    :eprof.start\n    :eprof.start_profiling([pid])\n  end\n\n  @doc \"\"\"\n  Stop the profiling previously started with start method call.\n  \"\"\"\n  def stop do\n    :eprof.stop_profiling\n  end\n\n  @doc \"\"\"\n  Analyze and output the profiling as the list of Prof records.\n  It also outputs to the STDOUT.\n  \"\"\"\n  def analyze do\n    random_number = :rand.uniform(100)\n    file_name = to_string(@tmp_prof_name ++ [to_string(random_number)])\n    :eprof.log(file_name)\n    :eprof.analyze(:total, [{:sort, :time}])\n    records = ExProf.Reader.read(file_name)\n    File.rm!(file_name)\n    records\n  end\nend\n"
  },
  {
    "path": "lib/sample/sample_runner.ex",
    "content": "defmodule SampleRunner do\n  import ExProf.Macro\n\n  @doc \"analyze with profile macro\"\n  def do_analyze do\n    profile do\n      :timer.sleep 2000\n      IO.puts \"message\\n\"\n    end\n  end\n\n  @doc \"get analysis records and sum them up\"\n  def run do\n    {records, result} = do_analyze()\n    total_percent = Enum.reduce(records, 0.0, &(&1.percent + &2))\n    IO.inspect \"total = #{total_percent}\"\n    result\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule ExProf.Mixfile do\n  use Mix.Project\n\n  def project do\n    [ app: :exprof,\n      version: \"0.2.4\",\n      elixir: \"~> 1.0\",\n      deps: deps(),\n      description: description(),\n      package: package()\n    ]\n  end\n\n  # Configuration for the OTP application\n  def application do\n    [\n      extra_applications: [:tools]\n    ]\n  end\n\n  # Returns the list of dependencies in the format:\n  # { :foobar, \"~> 0.1\", git: \"https://github.com/elixir-lang/foobar.git\" }\n  defp deps do\n    [\n      {:exprintf, \"~> 0.2\"},\n      {:ex_doc, \">= 0.0.0\", only: :dev}\n    ]\n  end\n\n  defp description do\n    \"\"\"\n    A simple code profiler for Elixir using eprof.\n    \"\"\"\n  end\n\n  defp package do\n    [ maintainers: [\"parroty\"],\n      licenses: [\"MIT\"],\n      links: %{\"GitHub\" => \"https://github.com/parroty/exprof\"} ]\n  end\nend\n"
  },
  {
    "path": "package.exs",
    "content": "Expm.Package.new(name: \"exprof\", description: \"A simple code profiler for Elixir using eprof\",\n                 version: \"0.0.1\", keywords: [\"Elixir\",\"eprof\",\"profiler\"],\n                 maintainers: [[name: \"parroty\", email: \"parroty00@gmail.com\"]],\n                 repositories: [[github: \"parroty/exprof\"]])\n"
  },
  {
    "path": "test/exprof_test.exs",
    "content": "defmodule ExprofTest do\n  use ExUnit.Case, async: false\n  import ExUnit.CaptureIO\n\n  test \"sample runner\" do\n    assert capture_io(fn ->\n      assert :ok = SampleRunner.run\n    end) =~ ~r/FUNCTION\\s+CALLS/m\n  end\n\n  @tag timeout: 1000\n  test \"abort on exit\" do\n    Process.flag(:trap_exit, true)\n\n    pid = spawn_link(fn ->\n      import ExProf.Macro\n\n      profile do\n        Process.exit(self(), :kill)\n      end\n    end)\n\n    assert_receive {:EXIT, ^pid, :killed}, 500\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start\n"
  }
]