Repository: parroty/exprof
Branch: master
Commit: d72e4247e39e
Files: 15
Total size: 11.2 KB
Directory structure:
gitextract_a41okyq2/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── lib/
│ ├── exprof/
│ │ ├── analyzer.ex
│ │ ├── macro.ex
│ │ ├── reader.ex
│ │ └── records.ex
│ ├── exprof.ex
│ └── sample/
│ └── sample_runner.ex
├── mix.exs
├── package.exs
└── test/
├── exprof_test.exs
└── test_helper.exs
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/tests.yml
================================================
name: tests
on: push
jobs:
tests:
name: Run Tests
runs-on: ubuntu-latest
strategy:
matrix:
otp: ['19.3']
elixir: ['1.5', '1.7']
env:
MIX_ENV: test
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-elixir@v1
with:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- uses: actions/cache@v1
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
restore-keys: |
${{ runner.os }}-mix-
- run: mix deps.get
- run: mix test --seed 0 # disable randomization
================================================
FILE: .gitignore
================================================
/ebin
/deps
erl_crash.dump
*.ez
tmp_exprof
/_build
/doc
================================================
FILE: CHANGELOG.md
================================================
0.2.4
------
#### Changes
* Makes sure it is OK for the profiled code to send/receive messages (#13).
* Fix for warnings with elixir v1.11.
* Add :tools to extra_applications in mix.exs (#15).
0.2.3
------
#### Changes
* Fixes issue on windows where you get File.Error permission denied.
- Changed to use randomized file name (#11).
0.2.2
------
#### Changes
* Return block result after completion (#9).
* Link profiled process to avoid hang (#10).
================================================
FILE: LICENSE
================================================
Copyright (c) 2013-2015 parroty
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
ExProf
============
[](https://github.com/parroty/exprof/actions)
[](https://hex.pm/packages/exprof)
[](https://hex.pm/packages/exprof)
A simple code profiler for Elixir using eprof.
It 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.
### Install
Add `:exprof` to `deps` section of `mix.exs`.
```elixir
def deps do
[ {:exprof, "~> 0.2.0"} ]
end
```
### Usage
import "ExProf.Macro", then use "profile" macro to start profiling. It
prints out results, and returns them as list of records, along with the
result of the profiled block.
```elixir
defmodule SampleRunner do
import ExProf.Macro
@doc "analyze with profile macro"
def do_analyze do
profile do
:timer.sleep 2000
IO.puts "message\n"
end
end
@doc "get analysis records and sum them up"
def run do
{records, _block_result} = do_analyze
total_percent = Enum.reduce(records, 0.0, &(&1.percent + &2))
IO.inspect "total = #{total_percent}"
end
end
```
### Run
An example to use in iex console.
```elixir
$ iex -S mix
..
iex(1)> SampleRunner.run
message
FUNCTION CALLS % TIME [uS / CALLS]
-------- ----- --- ---- [----------]
'Elixir.IO':puts/2 1 0.86 1 [ 1.00]
io:o_request/3 1 1.72 2 [ 2.00]
io:put_chars/2 1 1.72 2 [ 2.00]
erlang:group_leader/0 1 1.72 2 [ 2.00]
io:request/2 1 2.59 3 [ 3.00]
io:execute_request/2 1 2.59 3 [ 3.00]
'Elixir.SampleRunner':'-run/0-fun-0-'/0 1 2.59 3 [ 3.00]
'Elixir.IO':map_dev/1 1 3.45 4 [ 4.00]
erlang:demonitor/2 1 4.31 5 [ 5.00]
io:io_request/2 1 6.03 7 [ 7.00]
io:wait_io_mon_reply/2 1 6.90 8 [ 8.00]
'Elixir.IO':puts/1 1 8.62 10 [ 10.00]
unicode:characters_to_binary/2 1 11.21 13 [ 13.00]
timer:sleep/1 1 14.66 17 [ 17.00]
erlang:monitor/2 1 31.03 36 [ 36.00]
"total = 100.0"
```
### Add a Mix Task
An example to use as mix tasks.
```elixir
defmodule Mix.Tasks.Exprof do
@shortdoc "Profile using ExProf"
use Mix.Task
import ExProf.Macro
def run(_mix_args) do
profile do: do_work(2)
end
defp do_work(n) do
:timer.sleep(n * 1000)
end
end
```
================================================
FILE: lib/exprof/analyzer.ex
================================================
defmodule ExProf.Analyzer do
@moduledoc """
Proivdes helper methods for operating Prof records.
"""
import ExPrintf
@doc """
Returns records only have major percent values, and filter out rest of them.
The default is to pick "80%" and it can be changed by specifying rate argument.
"""
def get_top_percent_items(records, rate \\ 80.0) do
sorted_records = Enum.sort(records, &(&1.percent > &2.percent))
do_get_top_percent_items(sorted_records, rate, [])
end
defp do_get_top_percent_items(records, rate, acc) when length(records) == 0 or rate <= 0.0 do
Enum.reverse(acc)
end
defp do_get_top_percent_items([head|tail], rate, acc) do
do_get_top_percent_items(tail, rate - head.percent, [head|acc])
end
@doc """
Print records to screen
"""
def print(records) do
print_header()
Enum.each(records, &(do_print(&1)))
end
defp print_header do
IO.puts "FUNCTION CALLS % TIME [uS / CALLS]"
IO.puts "-------- ----- --- ---- [----------]"
end
defp do_print(record) do
printf("%-50s %-6d %6.2f %5d [%10.2f]\n",
[String.slice(record.function, 0, 50), record.calls, record.percent, record.time, record.us_per_call])
end
end
================================================
FILE: lib/exprof/macro.ex
================================================
defmodule ExProf.Macro do
@moduledoc """
Provides a macro to profile a block of code.
"""
@doc """
A macro to specify the code block to profile.
It spawns a new process to execute the code block for isolating the profile result.
(ex.)
profile do
:timer.sleep 2000
end
"""
defmacro profile(do: code) do
quote do
ref = make_ref()
pid = spawn_link(ExProf.Macro, :execute_profile, [fn -> unquote(code) end, ref])
ExProf.start(pid)
send pid, {ref, self()}
result =
receive do
{^ref, result} -> result
end
ExProf.stop
records = ExProf.analyze
{records, result}
end
end
@doc """
An internal method for initiating profiling.
"""
def execute_profile(func, ref) do
receive do
{^ref, sender} ->
send sender, {ref, func.()}
forward_other_messages(sender)
end
end
defp forward_other_messages(sender) do
receive do
message ->
send sender, message
forward_other_messages(sender)
end
end
end
================================================
FILE: lib/exprof/reader.ex
================================================
defmodule ExProf.Reader do
@moduledoc """
A reader for eprof outputs.
"""
@doc """
Read the eprof output file and returns the parsed result of Prof records.
"""
def read(file_name) do
File.read!(file_name) |> String.split("\n") |> parse([])
end
defp parse([], acc), do: Enum.reverse(acc)
defp parse([head|tail], acc) do
case parse_record(head) do
nil -> parse(tail, acc)
record -> parse(tail, [record|acc])
end
end
defp parse_record(head) do
case Regex.run(~r/(.+?\/[0-9])\s+(.+?)\s+(.+?)\s+(.+?)\s+\[\s+(.+?)\]/, head) do
[_all, function, calls, percent, time, us_per_call] ->
%Prof{function: function, calls: String.to_integer(calls), percent: String.to_float(percent),
time: String.to_integer(time), us_per_call: String.to_float(us_per_call)}
nil -> nil
end
end
end
================================================
FILE: lib/exprof/records.ex
================================================
defmodule Prof do
defstruct function: nil, calls: nil, percent: nil, time: nil, us_per_call: nil
end
================================================
FILE: lib/exprof.ex
================================================
defmodule ExProf do
@moduledoc """
Wrapper for eprof library.
It needs to be called in start -> stop -> analyze order.
"""
# temporary file for storing eprof output
@tmp_prof_name 'tmp_exprof'
@doc """
Start the profiling for the specified pid.
"""
def start(pid \\ self()) do
:eprof.start
:eprof.start_profiling([pid])
end
@doc """
Stop the profiling previously started with start method call.
"""
def stop do
:eprof.stop_profiling
end
@doc """
Analyze and output the profiling as the list of Prof records.
It also outputs to the STDOUT.
"""
def analyze do
random_number = :rand.uniform(100)
file_name = to_string(@tmp_prof_name ++ [to_string(random_number)])
:eprof.log(file_name)
:eprof.analyze(:total, [{:sort, :time}])
records = ExProf.Reader.read(file_name)
File.rm!(file_name)
records
end
end
================================================
FILE: lib/sample/sample_runner.ex
================================================
defmodule SampleRunner do
import ExProf.Macro
@doc "analyze with profile macro"
def do_analyze do
profile do
:timer.sleep 2000
IO.puts "message\n"
end
end
@doc "get analysis records and sum them up"
def run do
{records, result} = do_analyze()
total_percent = Enum.reduce(records, 0.0, &(&1.percent + &2))
IO.inspect "total = #{total_percent}"
result
end
end
================================================
FILE: mix.exs
================================================
defmodule ExProf.Mixfile do
use Mix.Project
def project do
[ app: :exprof,
version: "0.2.4",
elixir: "~> 1.0",
deps: deps(),
description: description(),
package: package()
]
end
# Configuration for the OTP application
def application do
[
extra_applications: [:tools]
]
end
# Returns the list of dependencies in the format:
# { :foobar, "~> 0.1", git: "https://github.com/elixir-lang/foobar.git" }
defp deps do
[
{:exprintf, "~> 0.2"},
{:ex_doc, ">= 0.0.0", only: :dev}
]
end
defp description do
"""
A simple code profiler for Elixir using eprof.
"""
end
defp package do
[ maintainers: ["parroty"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/parroty/exprof"} ]
end
end
================================================
FILE: package.exs
================================================
Expm.Package.new(name: "exprof", description: "A simple code profiler for Elixir using eprof",
version: "0.0.1", keywords: ["Elixir","eprof","profiler"],
maintainers: [[name: "parroty", email: "parroty00@gmail.com"]],
repositories: [[github: "parroty/exprof"]])
================================================
FILE: test/exprof_test.exs
================================================
defmodule ExprofTest do
use ExUnit.Case, async: false
import ExUnit.CaptureIO
test "sample runner" do
assert capture_io(fn ->
assert :ok = SampleRunner.run
end) =~ ~r/FUNCTION\s+CALLS/m
end
@tag timeout: 1000
test "abort on exit" do
Process.flag(:trap_exit, true)
pid = spawn_link(fn ->
import ExProf.Macro
profile do
Process.exit(self(), :kill)
end
end)
assert_receive {:EXIT, ^pid, :killed}, 500
end
end
================================================
FILE: test/test_helper.exs
================================================
ExUnit.start
gitextract_a41okyq2/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── lib/
│ ├── exprof/
│ │ ├── analyzer.ex
│ │ ├── macro.ex
│ │ ├── reader.ex
│ │ └── records.ex
│ ├── exprof.ex
│ └── sample/
│ └── sample_runner.ex
├── mix.exs
├── package.exs
└── test/
├── exprof_test.exs
└── test_helper.exs
SYMBOL INDEX (29 symbols across 8 files)
FILE: lib/exprof.ex
class ExProf (line 1) | defmodule ExProf
method start (line 13) | def start(pid \\ self()) do
method stop (line 21) | def stop do
method analyze (line 29) | def analyze do
FILE: lib/exprof/analyzer.ex
class ExProf.Analyzer (line 1) | defmodule ExProf.Analyzer
method get_top_percent_items (line 11) | def get_top_percent_items(records, rate \\ 80.0) do
method do_get_top_percent_items (line 20) | defp do_get_top_percent_items([head|tail], rate, acc) do
method print (line 27) | def print(records) do
method print_header (line 32) | defp print_header do
method do_print (line 37) | defp do_print(record) do
FILE: lib/exprof/macro.ex
class ExProf.Macro (line 1) | defmodule ExProf.Macro
method execute_profile (line 39) | def execute_profile(func, ref) do
method forward_other_messages (line 47) | defp forward_other_messages(sender) do
FILE: lib/exprof/reader.ex
class ExProf.Reader (line 1) | defmodule ExProf.Reader
method read (line 9) | def read(file_name) do
method parse (line 13) | defp parse([], acc), do: Enum.reverse(acc)
method parse (line 14) | defp parse([head|tail], acc) do
method parse_record (line 21) | defp parse_record(head) do
FILE: lib/exprof/records.ex
class Prof (line 1) | defmodule Prof
FILE: lib/sample/sample_runner.ex
class SampleRunner (line 1) | defmodule SampleRunner
method do_analyze (line 5) | def do_analyze do
method run (line 13) | def run do
FILE: mix.exs
class ExProf.Mixfile (line 1) | defmodule ExProf.Mixfile
method project (line 4) | def project do
method application (line 15) | def application do
method deps (line 23) | defp deps do
method description (line 30) | defp description do
method package (line 36) | defp package do
FILE: test/exprof_test.exs
class ExprofTest (line 1) | defmodule ExprofTest
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
{
"path": ".github/workflows/tests.yml",
"chars": 731,
"preview": "name: tests\n\non: push\n\njobs:\n tests:\n name: Run Tests\n runs-on: ubuntu-latest\n strategy:\n matrix:\n "
},
{
"path": ".gitignore",
"chars": 55,
"preview": "/ebin\n/deps\nerl_crash.dump\n*.ez\ntmp_exprof\n/_build\n/doc"
},
{
"path": "CHANGELOG.md",
"chars": 456,
"preview": "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"
},
{
"path": "LICENSE",
"chars": 1056,
"preview": "Copyright (c) 2013-2015 parroty\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this so"
},
{
"path": "README.md",
"chars": 2926,
"preview": "ExProf\n============\n\n[](https://github."
},
{
"path": "lib/exprof/analyzer.ex",
"chars": 1300,
"preview": "defmodule ExProf.Analyzer do\n @moduledoc \"\"\"\n Proivdes helper methods for operating Prof records.\n \"\"\"\n import ExPri"
},
{
"path": "lib/exprof/macro.ex",
"chars": 1081,
"preview": "defmodule ExProf.Macro do\n @moduledoc \"\"\"\n Provides a macro to profile a block of code.\n \"\"\"\n\n @doc \"\"\"\n A macro to"
},
{
"path": "lib/exprof/reader.ex",
"chars": 870,
"preview": "defmodule ExProf.Reader do\n @moduledoc \"\"\"\n A reader for eprof outputs.\n \"\"\"\n\n @doc \"\"\"\n Read the eprof output file"
},
{
"path": "lib/exprof/records.ex",
"chars": 103,
"preview": "defmodule Prof do\n defstruct function: nil, calls: nil, percent: nil, time: nil, us_per_call: nil\nend\n"
},
{
"path": "lib/exprof.ex",
"chars": 888,
"preview": "defmodule ExProf do\n @moduledoc \"\"\"\n Wrapper for eprof library.\n It needs to be called in start -> stop -> analyze or"
},
{
"path": "lib/sample/sample_runner.ex",
"chars": 410,
"preview": "defmodule SampleRunner do\n import ExProf.Macro\n\n @doc \"analyze with profile macro\"\n def do_analyze do\n profile do\n"
},
{
"path": "mix.exs",
"chars": 819,
"preview": "defmodule ExProf.Mixfile do\n use Mix.Project\n\n def project do\n [ app: :exprof,\n version: \"0.2.4\",\n elixir"
},
{
"path": "package.exs",
"chars": 313,
"preview": "Expm.Package.new(name: \"exprof\", description: \"A simple code profiler for Elixir using eprof\",\n version:"
},
{
"path": "test/exprof_test.exs",
"chars": 481,
"preview": "defmodule ExprofTest do\n use ExUnit.Case, async: false\n import ExUnit.CaptureIO\n\n test \"sample runner\" do\n assert "
},
{
"path": "test/test_helper.exs",
"chars": 13,
"preview": "ExUnit.start\n"
}
]
About this extraction
This page contains the full source code of the parroty/exprof GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (11.2 KB), approximately 3.7k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.