Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
knutin committed Sep 25, 2011
0 parents commit 5edfab1
Show file tree
Hide file tree
Showing 15 changed files with 969 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
*~
ebin
.eunit
erl_crash.dump
deps
91 changes: 91 additions & 0 deletions README
@@ -0,0 +1,91 @@
# gcprof - garbage collection profiler

gcprof is a tool to measure garbage collection in a production
system. For every message a process receives, gcprof tells you how
much time was spent in garbage collection and how many passes was
done.


## How does it work

`gcprof:trace(pid(), identity_f())` triggers tracing of garbage
collection in the specified process. Every time the process receives a
message, the resulting garbage collections is associated with that
message. The identity fun is used to identify that message.

The runtime of garbage collections is aggregated and grouped on the
identity. The results can be retrieved by calling
`gcprof_aggregator:get_stats/0`. This will reset the current stats, so
it is suitable for periodic polling.

The convenience method `gcprof:trace_me(identity_f())` can be used
from within the process you wish to trace.


## Identity

The identity fun is given the message your process received and must
produce an identity. The results will be grouped by identity, so it
makes sense to keep the number of unique ids low. The identity will be
the `key` in `gcprof_aggregator:get_stats/0`.

For example:

fun ({'$gen_call', {_, _}, do_some_stuff}) ->
my_identity;
({'$gen_call', {_, _}, {request, Arg1, Arg2}) ->
{request, Arg1};
(init) ->
init;
(_) ->
undefined
end

There is one special message you may also handle and that is
`init`. Any garbage collection done after triggering the trace, but
before your process received any messages is associated with this
identity. Please note that as starting a trace is asynchronous, some
information might be lost.


## Sampling

As you may not want to trace all processes executing the code that
triggers tracing, there is a very simple way of deciding which
processes to sample.

Example:

IdentityF = fun ...,
Id = 1000000,
Divisor = 100,
gcprof:sample_me(IdentityF, Id rem Divisor)

`sample_me/2` only samples if the second argument is 0. So in the
above case, given an even distribution of ids, only 1% will of the
processes be sampled.


## Impact on your system

`gcprof:trace/2` uses a `gen_server:cast/2` under the hood so your
process do not have to wait for gcprof. This also means that if gcprof
is not running, nothing happens.

If gcprof would crash, the processes would be restarted. When the
gcprof tracer process goes away, the VM stops whatever traces was
active.


## Ideas for the future

At the moment, there are some race conditions in some of the tests
that makes them occasionally fail. This should be fixed.

To trace code without modifying the target, a future version of gcprof
could trace a specific function call and start tracing of the process
invoking that function. Superuseful for the `init/1` gen_server
callback for example.



11 changes: 11 additions & 0 deletions bin/start.sh
@@ -0,0 +1,11 @@
#!/bin/sh
SCRIPT_PATH=`dirname $0`
cd $SCRIPT_PATH/..

erl -pa deps/*/ebin ebin \
-boot start_sasl \
-s gcprof \
-K true \
+P 1000000 \
+A 8 \
-name gcprof@$(hostname)
4 changes: 4 additions & 0 deletions rebar.config
@@ -0,0 +1,4 @@
{deps, [
{basho_stats, "1.0.1", {git, "git://github.com/basho/basho_stats.git", "master"}}
]}.
{cover_enabled, true}.
78 changes: 78 additions & 0 deletions src/example_server.erl
@@ -0,0 +1,78 @@
-module(example_server).

-behaviour(gen_server).

%% API
-export([run/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {}).

%%%===================================================================
%%% API
%%%===================================================================

start(Id) ->
gen_server:start(?MODULE, [Id], []).

req(Pid) ->
gen_server:call(Pid, create_garbage).

run(Servers, Requests) ->
RunF = fun(Id) ->
{ok, Pid} = start(Id),
timer:sleep(1),
[req(Pid) || _ <- lists:seq(1, Requests)],
exit(Pid, kill)
end,

lists:foreach(fun (Id) ->
spawn(fun () -> RunF(Id) end)
end, lists:seq(1, Servers)).


%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([Id]) ->
gcprof:sample_me(fun identity/1, Id rem 10),
{ok, #state{}}.

handle_call(create_garbage, _From, State) ->
Pid = spawn(fun() ->
receive _ -> ok
end
end),
L = lists:seq(1, 100000),
Pid ! L,
{reply, ok, State};

handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.

handle_cast(_Msg, State) ->
{noreply, State}.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

identity({'$gen_call', {_, _}, create_garbage}) -> create_garbage;
identity(_) -> undefined.

12 changes: 12 additions & 0 deletions src/gcprof.app.src
@@ -0,0 +1,12 @@
{application, gcprof,
[
{description, ""},
{vsn, "1"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, { gcprof_app, []}},
{env, []}
]}.
25 changes: 25 additions & 0 deletions src/gcprof.erl
@@ -0,0 +1,25 @@
-module(gcprof).
-export([start/0, stop/0]).
-export([trace/2, trace_me/1, sample_me/2]).

start() -> application:start(gcprof).
stop() -> application:stop(gcprof).

%% @doc: Starts tracing garbage collections in the given
%% process. Note: gen_server:cast is used, so at some point in the
%% future, tracing *might* start.
trace(Pid, IdentityF) ->
gen_server:cast(gcprof_server, {start_trace, Pid, IdentityF}).

%% @doc: Starts tracing garbage collections in the calling process.
trace_me(IdentityF) ->
trace(self(), IdentityF).

%% @doc: Traces the calling process if the second argument is
%% 0. Allows tracing if the result of a modulo operation is 0.
sample_me(IdentityF, 0) ->
trace(self(), IdentityF);
sample_me(_, _) ->
ok.


105 changes: 105 additions & 0 deletions src/gcprof_aggregator.erl
@@ -0,0 +1,105 @@
-module(gcprof_aggregator).

-behaviour(gen_server).

%% API
-export([start_link/0, results/4, get_stats/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

-define(SERVER, ?MODULE).
-define(NEW_HISTOGRAM, basho_stats_histogram:new(0, 1000000, 1000000)).

-record(state, {}).

%%%===================================================================
%%% API
%%%===================================================================

start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

results(Pid, Identity, Runtime, Invocations) ->
gen_server:cast(?MODULE, {results, Pid, Identity, Runtime, Invocations}).

get_stats() ->
gen_server:call(?MODULE, get_stats).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

init([]) ->
{ok, #state{}}.

handle_call(get_stats, _From, State) ->
{reply, {ok, do_get_stats()}, State};

handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.

handle_cast({results, _Pid, Identity, Runtime, Invocations}, State) ->
ok = update_histogram({Identity, runtime}, Runtime),
ok = update_histogram({Identity, invocations}, Invocations),
%%ok = update_histogram({Pid, runtime}, Runtime),
%%ok = update_histogram({Pid, invocations}, Invocations),
{noreply, State};

handle_cast(_Msg, State) ->
{noreply, State}.


handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

get_or_create_histogram(Key) ->
case get(Key) of
undefined -> ?NEW_HISTOGRAM;
{histogram, Value} -> Value
end.

update_histogram(Key, Value) ->
Hist = get_or_create_histogram(Key),
NewHist = basho_stats_histogram:update(Value, Hist),
erlang:put(Key, {histogram, NewHist}),
ok.


do_get_stats() ->
Ts = lists:filter(fun ({_, {histogram, Hist}}) ->
basho_stats_histogram:observations(Hist) =/= 0;
(_) ->
false
end, get()),
[erlang:put(Key, {histogram, ?NEW_HISTOGRAM}) || {Key, _Value} <- Ts],
lists:map(fun (T) -> format_timing(T) end, Ts).

format_timing({Identity, {histogram, Hist}}) ->
{Min, Mean, Max, _, SD} =
basho_stats_histogram:summary_stats(Hist),

[
{key, Identity},
{observations, basho_stats_histogram:observations(Hist)},
{min, Min},
{mean, Mean},
{max, Max},
{sd, SD},
{quantile_25, basho_stats_histogram:quantile(0.250, Hist)},
{quantile_75, basho_stats_histogram:quantile(0.750, Hist)},
{quantile_99, basho_stats_histogram:quantile(0.990, Hist)},
{quantile_999, basho_stats_histogram:quantile(0.999, Hist)}
].
16 changes: 16 additions & 0 deletions src/gcprof_app.erl
@@ -0,0 +1,16 @@
-module(gcprof_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

%% ===================================================================
%% Application callbacks
%% ===================================================================

start(_StartType, _StartArgs) ->
gcprof_sup:start_link().

stop(_State) ->
ok.

0 comments on commit 5edfab1

Please sign in to comment.