Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

basic cowboy app, and a butchered version of rb

  • Loading branch information...
commit 20dd0256dbece18fd0e25339da95d815bed32a7c 1 parent c0aab38
@RJ RJ authored
View
4 .gitignore
@@ -0,0 +1,4 @@
+.*
+*~
+*.beam
+*.dump
View
30 Makefile
@@ -0,0 +1,30 @@
+# See LICENSE for licensing information.
+
+DIALYZER = dialyzer
+REBAR = ./rebar
+
+all: app
+
+app:
+ @$(REBAR) compile
+
+clean:
+ @$(REBAR) clean
+ rm -f erl_crash.dump
+
+tests: clean app eunit ct
+
+eunit:
+ @$(REBAR) eunit
+
+ct:
+ @$(REBAR) ct
+
+build-plt:
+ @$(DIALYZER) --build_plt --output_plt .bigwig_dialyzer.plt \
+ --apps kernel stdlib sasl inets crypto public_key ssl
+
+dialyze:
+ @$(DIALYZER) --src src --plt .bigwig_dialyzer.plt \
+ -Wbehaviours -Werror_handling \
+ -Wrace_conditions -Wunmatched_returns # -Wunderspecs
View
5 README
@@ -0,0 +1,5 @@
+bigwig is a web interface to a running erlang system.
+
+you get stats on the state of the VM, releases, apps etc
+
+Also, a web-based report-browser, etop, appmon etc etc
View
12 apps/bigwig/src/bigwig.app.src
@@ -0,0 +1,12 @@
+{application, bigwig,
+ [
+ {description, "web-based suite of tools for interrogating and erlang system"},
+ {vsn, "0.1"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { bigwig_app, []}},
+ {env, []}
+ ]}.
View
19 apps/bigwig/src/bigwig.erl
@@ -0,0 +1,19 @@
+-module(bigwig).
+-export([start/0, stop/0]).
+
+ensure_started(App) ->
+ case application:start(App) of
+ ok ->
+ ok;
+ {error, {already_started, App}} ->
+ ok
+ end.
+
+start() ->
+ ensure_started(crypto),
+ ensure_started(sasl),
+ ensure_started(cowboy),
+ application:start(bigwig).
+
+stop() ->
+ application:stop(bigwig).
View
16 apps/bigwig/src/bigwig_app.erl
@@ -0,0 +1,16 @@
+-module(bigwig_app).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+%% ===================================================================
+%% Application callbacks
+%% ===================================================================
+
+start(_StartType, _StartArgs) ->
+ bigwig_sup:start_link().
+
+stop(_State) ->
+ ok.
View
58 apps/bigwig/src/bigwig_http.erl
@@ -0,0 +1,58 @@
+-module(bigwig_http).
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+
+-export([start_link/0]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-record(state, {}).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+
+init([]) ->
+
+ Dispatch = [
+ %% {Host, list({Path, Handler, Opts})}
+ {'_', [{'_', bigwig_http_vm, []}]}
+ ],
+
+ Port = 8080,
+ error_logger:info_msg("Starting http server on port ~p", [Port]),
+
+ %% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
+ cowboy:start_listener(http, 100,
+ cowboy_tcp_transport, [{port, Port}],
+ cowboy_http_protocol, [{dispatch, Dispatch}]
+ ),
+
+ {ok, #state{}}.
+
+handle_call(_Request, _From, State) ->
+ {noreply, ok, 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 Function Definitions
+%% ------------------------------------------------------------------
+
View
87 apps/bigwig/src/bigwig_http_vm.erl
@@ -0,0 +1,87 @@
+%%
+%% show details on the VM, releases, apps, etc.
+%%
+-module(bigwig_http_vm).
+-behaviour(cowboy_http_handler).
+-export([init/3, handle/2, terminate/2]).
+
+-compile(export_all).
+
+init({tcp, http}, Req, _Opts) ->
+ {ok, Req, undefined_state}.
+
+handle(Req, State) ->
+ Body = mochijson2:encode(all()),
+ Headers = [{<<"Content-Type">>, <<"application/json">>}],
+ {ok, Req2} = cowboy_http_req:reply(200, Headers, Body, Req),
+ {ok, Req2, State}.
+
+terminate(_Req, _State) ->
+ ok.
+
+
+all() ->
+ {struct, [
+ {system_info, {struct, system_info()}},
+ {releases, {struct, releases()}},
+ {applications,{struct, applications()}}
+ ]}.
+
+
+%% Funs to load data about aspects of the system, mochiJSON formatted:
+
+system_info() ->
+ Fmt = fun(V) when is_number(V) -> V ;
+ (V) when is_list(V) -> list_to_binary(V) ;
+ (V) when is_atom(V) -> V end,
+
+ Keys = [
+ process_count,
+ process_limit,
+ kernel_poll,
+ logical_processors,
+ otp_release,
+ system_architecture
+ ],
+
+ [ {N, Fmt(erlang:system_info(N))} || N <- Keys ].
+
+
+releases() ->
+ Rels = release_handler:which_releases(),
+ %% TODO parse out the name/version of the apps?
+ FmtDeps = fun(L) -> [ list_to_binary(A) || A <- L ] end,
+ [
+ {list_to_binary(Name), {struct,
+ [
+ {version, list_to_binary(Version)},
+ {status, list_to_binary(atom_to_list(Status))},
+ {deps, FmtDeps(Deps)}
+ ]}}
+ || {Name, Version, Deps, Status} <- Rels
+ ].
+
+
+applications() ->
+ %% Treat loaded as (Loaded - Running)
+ Which = application:which_applications(),
+ Loaded0 = application:loaded_applications(),
+ Loaded = lists:filter(fun({Name, _Desc, _Ver}) ->
+ lists:keyfind(Name, 1, Which) =:= false
+ end, Loaded0),
+
+ Format = fun(AppList) ->
+ {struct,
+ [ {Name,
+ {struct, [ {description, list_to_binary(Desc)},
+ {version, list_to_binary(Ver)} ]} }
+ || {Name, Desc, Ver} <- AppList
+ ]}
+ end,
+
+ [
+ {running, Format(Which)},
+ {loaded, Format(Loaded)}
+ ].
+
+
View
31 apps/bigwig/src/bigwig_sup.erl
@@ -0,0 +1,31 @@
+-module(bigwig_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ Http = ?CHILD(bigwig_http, worker),
+
+ Specs = [ Http ],
+
+ {ok, { {one_for_one, 5, 10}, Specs} }.
+
View
849 apps/bigwig/src/mochijson2.erl
@@ -0,0 +1,849 @@
+%% @author Bob Ippolito <bob@mochimedia.com>
+%% @copyright 2007 Mochi Media, Inc.
+
+%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works
+%% with binaries as strings, arrays as lists (without an {array, _})
+%% wrapper and it only knows how to decode UTF-8 (and ASCII).
+%%
+%% JSON terms are decoded as follows (javascript -> erlang):
+%% <ul>
+%% <li>{"key": "value"} ->
+%% {struct, [{&lt;&lt;"key">>, &lt;&lt;"value">>}]}</li>
+%% <li>["array", 123, 12.34, true, false, null] ->
+%% [&lt;&lt;"array">>, 123, 12.34, true, false, null]
+%% </li>
+%% </ul>
+%% <ul>
+%% <li>Strings in JSON decode to UTF-8 binaries in Erlang</li>
+%% <li>Objects decode to {struct, PropList}</li>
+%% <li>Numbers decode to integer or float</li>
+%% <li>true, false, null decode to their respective terms.</li>
+%% </ul>
+%% The encoder will accept the same format that the decoder will produce,
+%% but will also allow additional cases for leniency:
+%% <ul>
+%% <li>atoms other than true, false, null will be considered UTF-8
+%% strings (even as a proplist key)
+%% </li>
+%% <li>{json, IoList} will insert IoList directly into the output
+%% with no validation
+%% </li>
+%% <li>{array, Array} will be encoded as Array
+%% (legacy mochijson style)
+%% </li>
+%% <li>A non-empty raw proplist will be encoded as an object as long
+%% as the first pair does not have an atom key of json, struct,
+%% or array
+%% </li>
+%% </ul>
+
+-module(mochijson2).
+-author('bob@mochimedia.com').
+-export([encoder/1, encode/1]).
+-export([decoder/1, decode/1]).
+
+% This is a macro to placate syntax highlighters..
+-define(Q, $\").
+-define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset,
+ column=N+S#decoder.column}).
+-define(INC_COL(S), S#decoder{offset=1+S#decoder.offset,
+ column=1+S#decoder.column}).
+-define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset,
+ column=1,
+ line=1+S#decoder.line}).
+-define(INC_CHAR(S, C),
+ case C of
+ $\n ->
+ S#decoder{column=1,
+ line=1+S#decoder.line,
+ offset=1+S#decoder.offset};
+ _ ->
+ S#decoder{column=1+S#decoder.column,
+ offset=1+S#decoder.offset}
+ end).
+-define(IS_WHITESPACE(C),
+ (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
+
+%% @type iolist() = [char() | binary() | iolist()]
+%% @type iodata() = iolist() | binary()
+%% @type json_string() = atom | binary()
+%% @type json_number() = integer() | float()
+%% @type json_array() = [json_term()]
+%% @type json_object() = {struct, [{json_string(), json_term()}]}
+%% @type json_iolist() = {json, iolist()}
+%% @type json_term() = json_string() | json_number() | json_array() |
+%% json_object() | json_iolist()
+
+-record(encoder, {handler=null,
+ utf8=false}).
+
+-record(decoder, {object_hook=null,
+ offset=0,
+ line=1,
+ column=1,
+ state=null}).
+
+%% @spec encoder([encoder_option()]) -> function()
+%% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
+encoder(Options) ->
+ State = parse_encoder_options(Options, #encoder{}),
+ fun (O) -> json_encode(O, State) end.
+
+%% @spec encode(json_term()) -> iolist()
+%% @doc Encode the given as JSON to an iolist.
+encode(Any) ->
+ json_encode(Any, #encoder{}).
+
+%% @spec decoder([decoder_option()]) -> function()
+%% @doc Create a decoder/1 with the given options.
+decoder(Options) ->
+ State = parse_decoder_options(Options, #decoder{}),
+ fun (O) -> json_decode(O, State) end.
+
+%% @spec decode(iolist()) -> json_term()
+%% @doc Decode the given iolist to Erlang terms.
+decode(S) ->
+ json_decode(S, #decoder{}).
+
+%% Internal API
+
+parse_encoder_options([], State) ->
+ State;
+parse_encoder_options([{handler, Handler} | Rest], State) ->
+ parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+ parse_encoder_options(Rest, State#encoder{utf8=Switch}).
+
+parse_decoder_options([], State) ->
+ State;
+parse_decoder_options([{object_hook, Hook} | Rest], State) ->
+ parse_decoder_options(Rest, State#decoder{object_hook=Hook}).
+
+json_encode(true, _State) ->
+ <<"true">>;
+json_encode(false, _State) ->
+ <<"false">>;
+json_encode(null, _State) ->
+ <<"null">>;
+json_encode(I, _State) when is_integer(I) ->
+ integer_to_list(I);
+json_encode(F, _State) when is_float(F) ->
+ mochinum:digits(F);
+json_encode(S, State) when is_binary(S); is_atom(S) ->
+ json_encode_string(S, State);
+json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso
+ K =/= array andalso
+ K =/= json) ->
+ json_encode_proplist(Props, State);
+json_encode({struct, Props}, State) when is_list(Props) ->
+ json_encode_proplist(Props, State);
+json_encode(Array, State) when is_list(Array) ->
+ json_encode_array(Array, State);
+json_encode({array, Array}, State) when is_list(Array) ->
+ json_encode_array(Array, State);
+json_encode({json, IoList}, _State) ->
+ IoList;
+json_encode(Bad, #encoder{handler=null}) ->
+ exit({json_encode, {bad_term, Bad}});
+json_encode(Bad, State=#encoder{handler=Handler}) ->
+ json_encode(Handler(Bad), State).
+
+json_encode_array([], _State) ->
+ <<"[]">>;
+json_encode_array(L, State) ->
+ F = fun (O, Acc) ->
+ [$,, json_encode(O, State) | Acc]
+ end,
+ [$, | Acc1] = lists:foldl(F, "[", L),
+ lists:reverse([$\] | Acc1]).
+
+json_encode_proplist([], _State) ->
+ <<"{}">>;
+json_encode_proplist(Props, State) ->
+ F = fun ({K, V}, Acc) ->
+ KS = json_encode_string(K, State),
+ VS = json_encode(V, State),
+ [$,, VS, $:, KS | Acc]
+ end,
+ [$, | Acc1] = lists:foldl(F, "{", Props),
+ lists:reverse([$\} | Acc1]).
+
+json_encode_string(A, State) when is_atom(A) ->
+ L = atom_to_list(A),
+ case json_string_is_safe(L) of
+ true ->
+ [?Q, L, ?Q];
+ false ->
+ json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
+ end;
+json_encode_string(B, State) when is_binary(B) ->
+ case json_bin_is_safe(B) of
+ true ->
+ [?Q, B, ?Q];
+ false ->
+ json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
+ end;
+json_encode_string(I, _State) when is_integer(I) ->
+ [?Q, integer_to_list(I), ?Q];
+json_encode_string(L, State) when is_list(L) ->
+ case json_string_is_safe(L) of
+ true ->
+ [?Q, L, ?Q];
+ false ->
+ json_encode_string_unicode(L, State, [?Q])
+ end.
+
+json_string_is_safe([]) ->
+ true;
+json_string_is_safe([C | Rest]) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ json_string_is_safe(Rest);
+ _ ->
+ false
+ end.
+
+json_bin_is_safe(<<>>) ->
+ true;
+json_bin_is_safe(<<C, Rest/binary>>) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f ->
+ false;
+ C when C < 16#7f ->
+ json_bin_is_safe(Rest)
+ end.
+
+json_encode_string_unicode([], _State, Acc) ->
+ lists:reverse([$\" | Acc]);
+json_encode_string_unicode([C | Cs], State, Acc) ->
+ Acc1 = case C of
+ ?Q ->
+ [?Q, $\\ | Acc];
+ %% Escaping solidus is only useful when trying to protect
+ %% against "</script>" injection attacks which are only
+ %% possible when JSON is inserted into a HTML document
+ %% in-line. mochijson2 does not protect you from this, so
+ %% if you do insert directly into HTML then you need to
+ %% uncomment the following case or escape the output of encode.
+ %%
+ %% $/ ->
+ %% [$/, $\\ | Acc];
+ %%
+ $\\ ->
+ [$\\, $\\ | Acc];
+ $\b ->
+ [$b, $\\ | Acc];
+ $\f ->
+ [$f, $\\ | Acc];
+ $\n ->
+ [$n, $\\ | Acc];
+ $\r ->
+ [$r, $\\ | Acc];
+ $\t ->
+ [$t, $\\ | Acc];
+ C when C >= 0, C < $\s ->
+ [unihex(C) | Acc];
+ C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
+ [xmerl_ucs:to_utf8(C) | Acc];
+ C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
+ [unihex(C) | Acc];
+ C when C < 16#7f ->
+ [C | Acc];
+ _ ->
+ exit({json_encode, {bad_char, C}})
+ end,
+ json_encode_string_unicode(Cs, State, Acc1).
+
+hexdigit(C) when C >= 0, C =< 9 ->
+ C + $0;
+hexdigit(C) when C =< 15 ->
+ C + $a - 10.
+
+unihex(C) when C < 16#10000 ->
+ <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>,
+ Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]],
+ [$\\, $u | Digits];
+unihex(C) when C =< 16#10FFFF ->
+ N = C - 16#10000,
+ S1 = 16#d800 bor ((N bsr 10) band 16#3ff),
+ S2 = 16#dc00 bor (N band 16#3ff),
+ [unihex(S1), unihex(S2)].
+
+json_decode(L, S) when is_list(L) ->
+ json_decode(iolist_to_binary(L), S);
+json_decode(B, S) ->
+ {Res, S1} = decode1(B, S),
+ {eof, _} = tokenize(B, S1#decoder{state=trim}),
+ Res.
+
+decode1(B, S=#decoder{state=null}) ->
+ case tokenize(B, S#decoder{state=any}) of
+ {{const, C}, S1} ->
+ {C, S1};
+ {start_array, S1} ->
+ decode_array(B, S1);
+ {start_object, S1} ->
+ decode_object(B, S1)
+ end.
+
+make_object(V, #decoder{object_hook=null}) ->
+ V;
+make_object(V, #decoder{object_hook=Hook}) ->
+ Hook(V).
+
+decode_object(B, S) ->
+ decode_object(B, S#decoder{state=key}, []).
+
+decode_object(B, S=#decoder{state=key}, Acc) ->
+ case tokenize(B, S) of
+ {end_object, S1} ->
+ V = make_object({struct, lists:reverse(Acc)}, S1),
+ {V, S1#decoder{state=null}};
+ {{const, K}, S1} ->
+ {colon, S2} = tokenize(B, S1),
+ {V, S3} = decode1(B, S2#decoder{state=null}),
+ decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc])
+ end;
+decode_object(B, S=#decoder{state=comma}, Acc) ->
+ case tokenize(B, S) of
+ {end_object, S1} ->
+ V = make_object({struct, lists:reverse(Acc)}, S1),
+ {V, S1#decoder{state=null}};
+ {comma, S1} ->
+ decode_object(B, S1#decoder{state=key}, Acc)
+ end.
+
+decode_array(B, S) ->
+ decode_array(B, S#decoder{state=any}, []).
+
+decode_array(B, S=#decoder{state=any}, Acc) ->
+ case tokenize(B, S) of
+ {end_array, S1} ->
+ {lists:reverse(Acc), S1#decoder{state=null}};
+ {start_array, S1} ->
+ {Array, S2} = decode_array(B, S1),
+ decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
+ {start_object, S1} ->
+ {Array, S2} = decode_object(B, S1),
+ decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
+ {{const, Const}, S1} ->
+ decode_array(B, S1#decoder{state=comma}, [Const | Acc])
+ end;
+decode_array(B, S=#decoder{state=comma}, Acc) ->
+ case tokenize(B, S) of
+ {end_array, S1} ->
+ {lists:reverse(Acc), S1#decoder{state=null}};
+ {comma, S1} ->
+ decode_array(B, S1#decoder{state=any}, Acc)
+ end.
+
+tokenize_string(B, S=#decoder{offset=O}) ->
+ case tokenize_string_fast(B, O) of
+ {escape, O1} ->
+ Length = O1 - O,
+ S1 = ?ADV_COL(S, Length),
+ <<_:O/binary, Head:Length/binary, _/binary>> = B,
+ tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
+ O1 ->
+ Length = O1 - O,
+ <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
+ {{const, String}, ?ADV_COL(S, Length + 1)}
+ end.
+
+tokenize_string_fast(B, O) ->
+ case B of
+ <<_:O/binary, ?Q, _/binary>> ->
+ O;
+ <<_:O/binary, $\\, _/binary>> ->
+ {escape, O};
+ <<_:O/binary, C1, _/binary>> when C1 < 128 ->
+ tokenize_string_fast(B, 1 + O);
+ <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223,
+ C2 >= 128, C2 =< 191 ->
+ tokenize_string_fast(B, 2 + O);
+ <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239,
+ C2 >= 128, C2 =< 191,
+ C3 >= 128, C3 =< 191 ->
+ tokenize_string_fast(B, 3 + O);
+ <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244,
+ C2 >= 128, C2 =< 191,
+ C3 >= 128, C3 =< 191,
+ C4 >= 128, C4 =< 191 ->
+ tokenize_string_fast(B, 4 + O);
+ _ ->
+ throw(invalid_utf8)
+ end.
+
+tokenize_string(B, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, ?Q, _/binary>> ->
+ {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)};
+ <<_:O/binary, "\\\"", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]);
+ <<_:O/binary, "\\\\", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]);
+ <<_:O/binary, "\\/", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]);
+ <<_:O/binary, "\\b", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]);
+ <<_:O/binary, "\\f", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]);
+ <<_:O/binary, "\\n", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]);
+ <<_:O/binary, "\\r", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]);
+ <<_:O/binary, "\\t", _/binary>> ->
+ tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]);
+ <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> ->
+ C = erlang:list_to_integer([C3, C2, C1, C0], 16),
+ if C > 16#D7FF, C < 16#DC00 ->
+ %% coalesce UTF-16 surrogate pair
+ <<"\\u", D3, D2, D1, D0, _/binary>> = Rest,
+ D = erlang:list_to_integer([D3,D2,D1,D0], 16),
+ [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer,
+ D:16/big-unsigned-integer>>),
+ Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc),
+ tokenize_string(B, ?ADV_COL(S, 12), Acc1);
+ true ->
+ Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc),
+ tokenize_string(B, ?ADV_COL(S, 6), Acc1)
+ end;
+ <<_:O/binary, C1, _/binary>> when C1 < 128 ->
+ tokenize_string(B, ?INC_CHAR(S, C1), [C1 | Acc]);
+ <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223,
+ C2 >= 128, C2 =< 191 ->
+ tokenize_string(B, ?ADV_COL(S, 2), [C2, C1 | Acc]);
+ <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239,
+ C2 >= 128, C2 =< 191,
+ C3 >= 128, C3 =< 191 ->
+ tokenize_string(B, ?ADV_COL(S, 3), [C3, C2, C1 | Acc]);
+ <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244,
+ C2 >= 128, C2 =< 191,
+ C3 >= 128, C3 =< 191,
+ C4 >= 128, C4 =< 191 ->
+ tokenize_string(B, ?ADV_COL(S, 4), [C4, C3, C2, C1 | Acc]);
+ _ ->
+ throw(invalid_utf8)
+ end.
+
+tokenize_number(B, S) ->
+ case tokenize_number(B, sign, S, []) of
+ {{int, Int}, S1} ->
+ {{const, list_to_integer(Int)}, S1};
+ {{float, Float}, S1} ->
+ {{const, list_to_float(Float)}, S1}
+ end.
+
+tokenize_number(B, sign, S=#decoder{offset=O}, []) ->
+ case B of
+ <<_:O/binary, $-, _/binary>> ->
+ tokenize_number(B, int, ?INC_COL(S), [$-]);
+ _ ->
+ tokenize_number(B, int, S, [])
+ end;
+tokenize_number(B, int, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, $0, _/binary>> ->
+ tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]);
+ <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 ->
+ tokenize_number(B, int1, ?INC_COL(S), [C | Acc])
+ end;
+tokenize_number(B, int1, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+ tokenize_number(B, int1, ?INC_COL(S), [C | Acc]);
+ _ ->
+ tokenize_number(B, frac, S, Acc)
+ end;
+tokenize_number(B, frac, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 ->
+ tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]);
+ <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
+ tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]);
+ _ ->
+ {{int, lists:reverse(Acc)}, S}
+ end;
+tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+ tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]);
+ <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
+ tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]);
+ _ ->
+ {{float, lists:reverse(Acc)}, S}
+ end;
+tokenize_number(B, esign, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ ->
+ tokenize_number(B, eint, ?INC_COL(S), [C | Acc]);
+ _ ->
+ tokenize_number(B, eint, S, Acc)
+ end;
+tokenize_number(B, eint, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+ tokenize_number(B, eint1, ?INC_COL(S), [C | Acc])
+ end;
+tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+ tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]);
+ _ ->
+ {{float, lists:reverse(Acc)}, S}
+ end.
+
+tokenize(B, S=#decoder{offset=O}) ->
+ case B of
+ <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) ->
+ tokenize(B, ?INC_CHAR(S, C));
+ <<_:O/binary, "{", _/binary>> ->
+ {start_object, ?INC_COL(S)};
+ <<_:O/binary, "}", _/binary>> ->
+ {end_object, ?INC_COL(S)};
+ <<_:O/binary, "[", _/binary>> ->
+ {start_array, ?INC_COL(S)};
+ <<_:O/binary, "]", _/binary>> ->
+ {end_array, ?INC_COL(S)};
+ <<_:O/binary, ",", _/binary>> ->
+ {comma, ?INC_COL(S)};
+ <<_:O/binary, ":", _/binary>> ->
+ {colon, ?INC_COL(S)};
+ <<_:O/binary, "null", _/binary>> ->
+ {{const, null}, ?ADV_COL(S, 4)};
+ <<_:O/binary, "true", _/binary>> ->
+ {{const, true}, ?ADV_COL(S, 4)};
+ <<_:O/binary, "false", _/binary>> ->
+ {{const, false}, ?ADV_COL(S, 5)};
+ <<_:O/binary, "\"", _/binary>> ->
+ tokenize_string(B, ?INC_COL(S));
+ <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9)
+ orelse C =:= $- ->
+ tokenize_number(B, S);
+ <<_:O/binary>> ->
+ trim = S#decoder.state,
+ {eof, S}
+ end.
+%%
+%% Tests
+%%
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+%% testing constructs borrowed from the Yaws JSON implementation.
+
+%% Create an object from a list of Key/Value pairs.
+
+obj_new() ->
+ {struct, []}.
+
+is_obj({struct, Props}) ->
+ F = fun ({K, _}) when is_binary(K) -> true end,
+ lists:all(F, Props).
+
+obj_from_list(Props) ->
+ Obj = {struct, Props},
+ ?assert(is_obj(Obj)),
+ Obj.
+
+%% Test for equivalence of Erlang terms.
+%% Due to arbitrary order of construction, equivalent objects might
+%% compare unequal as erlang terms, so we need to carefully recurse
+%% through aggregates (tuples and objects).
+
+equiv({struct, Props1}, {struct, Props2}) ->
+ equiv_object(Props1, Props2);
+equiv(L1, L2) when is_list(L1), is_list(L2) ->
+ equiv_list(L1, L2);
+equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
+equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2;
+equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true.
+
+%% Object representation and traversal order is unknown.
+%% Use the sledgehammer and sort property lists.
+
+equiv_object(Props1, Props2) ->
+ L1 = lists:keysort(1, Props1),
+ L2 = lists:keysort(1, Props2),
+ Pairs = lists:zip(L1, L2),
+ true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
+ equiv(K1, K2) and equiv(V1, V2)
+ end, Pairs).
+
+%% Recursively compare tuple elements for equivalence.
+
+equiv_list([], []) ->
+ true;
+equiv_list([V1 | L1], [V2 | L2]) ->
+ equiv(V1, V2) andalso equiv_list(L1, L2).
+
+decode_test() ->
+ [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
+ <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]).
+
+e2j_vec_test() ->
+ test_one(e2j_test_vec(utf8), 1).
+
+test_one([], _N) ->
+ %% io:format("~p tests passed~n", [N-1]),
+ ok;
+test_one([{E, J} | Rest], N) ->
+ %% io:format("[~p] ~p ~p~n", [N, E, J]),
+ true = equiv(E, decode(J)),
+ true = equiv(E, decode(encode(E))),
+ test_one(Rest, 1+N).
+
+e2j_test_vec(utf8) ->
+ [
+ {1, "1"},
+ {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes
+ {-1, "-1"},
+ {-3.1416, "-3.14160"},
+ {12.0e10, "1.20000e+11"},
+ {1.234E+10, "1.23400e+10"},
+ {-1.234E-10, "-1.23400e-10"},
+ {10.0, "1.0e+01"},
+ {123.456, "1.23456E+2"},
+ {10.0, "1e1"},
+ {<<"foo">>, "\"foo\""},
+ {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""},
+ {<<"">>, "\"\""},
+ {<<"\n\n\n">>, "\"\\n\\n\\n\""},
+ {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""},
+ {obj_new(), "{}"},
+ {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"},
+ {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]),
+ "{\"foo\":\"bar\",\"baz\":123}"},
+ {[], "[]"},
+ {[[]], "[[]]"},
+ {[1, <<"foo">>], "[1,\"foo\"]"},
+
+ %% json array in a json object
+ {obj_from_list([{<<"foo">>, [123]}]),
+ "{\"foo\":[123]}"},
+
+ %% json object in a json object
+ {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]),
+ "{\"foo\":{\"bar\":true}}"},
+
+ %% fold evaluation order
+ {obj_from_list([{<<"foo">>, []},
+ {<<"bar">>, obj_from_list([{<<"baz">>, true}])},
+ {<<"alice">>, <<"bob">>}]),
+ "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
+
+ %% json object in a json array
+ {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
+ "[-123,\"foo\",{\"bar\":[]},null]"}
+ ].
+
+%% test utf8 encoding
+encoder_utf8_test() ->
+ %% safe conversion case (default)
+ [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
+ encode(<<1,"\321\202\320\265\321\201\321\202">>),
+
+ %% raw utf8 output (optional)
+ Enc = mochijson2:encoder([{utf8, true}]),
+ [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
+ Enc(<<1,"\321\202\320\265\321\201\321\202">>).
+
+input_validation_test() ->
+ Good = [
+ {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, %% pound
+ {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, %% euro
+ {16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} %% denarius
+ ],
+ lists:foreach(fun({CodePoint, UTF8}) ->
+ Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)),
+ Expect = decode(UTF8)
+ end, Good),
+
+ Bad = [
+ %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte
+ <<?Q, 16#80, ?Q>>,
+ %% missing continuations, last byte in each should be 80-BF
+ <<?Q, 16#C2, 16#7F, ?Q>>,
+ <<?Q, 16#E0, 16#80,16#7F, ?Q>>,
+ <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>,
+ %% we don't support code points > 10FFFF per RFC 3629
+ <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>>,
+ %% escape characters trigger a different code path
+ <<?Q, $\\, $\n, 16#80, ?Q>>
+ ],
+ lists:foreach(
+ fun(X) ->
+ ok = try decode(X) catch invalid_utf8 -> ok end,
+ %% could be {ucs,{bad_utf8_character_code}} or
+ %% {json_encode,{bad_char,_}}
+ {'EXIT', _} = (catch encode(X))
+ end, Bad).
+
+inline_json_test() ->
+ ?assertEqual(<<"\"iodata iodata\"">>,
+ iolist_to_binary(
+ encode({json, [<<"\"iodata">>, " iodata\""]}))),
+ ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]},
+ decode(
+ encode({struct,
+ [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))),
+ ok.
+
+big_unicode_test() ->
+ UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)),
+ ?assertEqual(
+ <<"\"\\ud834\\udd20\"">>,
+ iolist_to_binary(encode(UTF8Seq))),
+ ?assertEqual(
+ UTF8Seq,
+ decode(iolist_to_binary(encode(UTF8Seq)))),
+ ok.
+
+custom_decoder_test() ->
+ ?assertEqual(
+ {struct, [{<<"key">>, <<"value">>}]},
+ (decoder([]))("{\"key\": \"value\"}")),
+ F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end,
+ ?assertEqual(
+ win,
+ (decoder([{object_hook, F}]))("{\"key\": \"value\"}")),
+ ok.
+
+atom_test() ->
+ %% JSON native atoms
+ [begin
+ ?assertEqual(A, decode(atom_to_list(A))),
+ ?assertEqual(iolist_to_binary(atom_to_list(A)),
+ iolist_to_binary(encode(A)))
+ end || A <- [true, false, null]],
+ %% Atom to string
+ ?assertEqual(
+ <<"\"foo\"">>,
+ iolist_to_binary(encode(foo))),
+ ?assertEqual(
+ <<"\"\\ud834\\udd20\"">>,
+ iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))),
+ ok.
+
+key_encode_test() ->
+ %% Some forms are accepted as keys that would not be strings in other
+ %% cases
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode({struct, [{foo, 1}]}))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode({struct, [{"foo", 1}]}))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{foo, 1}]))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{<<"foo">>, 1}]))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{"foo", 1}]))),
+ ?assertEqual(
+ <<"{\"\\ud834\\udd20\":1}">>,
+ iolist_to_binary(
+ encode({struct, [{[16#0001d120], 1}]}))),
+ ?assertEqual(
+ <<"{\"1\":1}">>,
+ iolist_to_binary(encode({struct, [{1, 1}]}))),
+ ok.
+
+unsafe_chars_test() ->
+ Chars = "\"\\\b\f\n\r\t",
+ [begin
+ ?assertEqual(false, json_string_is_safe([C])),
+ ?assertEqual(false, json_bin_is_safe(<<C>>)),
+ ?assertEqual(<<C>>, decode(encode(<<C>>)))
+ end || C <- Chars],
+ ?assertEqual(
+ false,
+ json_string_is_safe([16#0001d120])),
+ ?assertEqual(
+ false,
+ json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))),
+ ?assertEqual(
+ [16#0001d120],
+ xmerl_ucs:from_utf8(
+ binary_to_list(
+ decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))),
+ ?assertEqual(
+ false,
+ json_string_is_safe([16#110000])),
+ ?assertEqual(
+ false,
+ json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))),
+ %% solidus can be escaped but isn't unsafe by default
+ ?assertEqual(
+ <<"/">>,
+ decode(<<"\"\\/\"">>)),
+ ok.
+
+int_test() ->
+ ?assertEqual(0, decode("0")),
+ ?assertEqual(1, decode("1")),
+ ?assertEqual(11, decode("11")),
+ ok.
+
+large_int_test() ->
+ ?assertEqual(<<"-2147483649214748364921474836492147483649">>,
+ iolist_to_binary(encode(-2147483649214748364921474836492147483649))),
+ ?assertEqual(<<"2147483649214748364921474836492147483649">>,
+ iolist_to_binary(encode(2147483649214748364921474836492147483649))),
+ ok.
+
+float_test() ->
+ ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649.0))),
+ ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648.0))),
+ ok.
+
+handler_test() ->
+ ?assertEqual(
+ {'EXIT',{json_encode,{bad_term,{}}}},
+ catch encode({})),
+ F = fun ({}) -> [] end,
+ ?assertEqual(
+ <<"[]">>,
+ iolist_to_binary((encoder([{handler, F}]))({}))),
+ ok.
+
+-endif.
View
49 apps/bigwig/src/myserver.erl
@@ -0,0 +1,49 @@
+-module(myserver).
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([start_link/0]).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+
+init(Args) ->
+ {ok, Args}.
+
+handle_call(_Request, _From, State) ->
+ {noreply, ok, 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 Function Definitions
+%% ------------------------------------------------------------------
+
View
504 apps/bigwig/src/rb2.erl
@@ -0,0 +1,504 @@
+%% This is a bare bones version of sasl/rb, but modified to load reports and
+%% return them from calls, instead of printing to stdout.
+%%
+%% Also, all filtering/grepping of reports has been removed for now.
+%%
+-module(rb2).
+
+-behaviour(gen_server).
+
+-export([start_link/1, start/0, start/1, stop/0, rescan/0, rescan/1]).
+-export([load_number/1, load_list/0, load_list/1]).
+
+%% gen_server callbacks
+-export([init/1, terminate/2, handle_call/3,
+ handle_cast/2, handle_info/2, code_change/3]).
+
+-record(state, {dir, data, device, max, type, abort, log}).
+
+%%-----------------------------------------------------------------
+start() -> start([]).
+start(Options) ->
+ supervisor:start_child(sasl_sup,
+ {rb2_server, {rb2, start_link, [Options]},
+ temporary, brutal_kill, worker, [rb2]}).
+
+start_link(Options) ->
+ gen_server:start_link({local, rb2_server}, rb2, Options, []).
+
+stop() ->
+ supervisor:terminate_child(sasl_sup, rb2_server).
+
+rescan() -> rescan([]).
+rescan(Options) ->
+ call({rescan, Options}).
+
+
+load_list() -> load_list(all).
+
+load_list(Type) -> call({load_list, Type}).
+
+%% Load a report by its number
+load_number(Number) when is_integer(Number) ->
+ call({load_number, Number}).
+
+%%-----------------------------------------------------------------
+
+call(Req) ->
+ gen_server:call(rb2_server, Req, infinity).
+
+%%-----------------------------------------------------------------
+
+init(Options) ->
+ process_flag(priority, low),
+ process_flag(trap_exit, true),
+ Log = get_option(Options, start_log, standard_io),
+ Device = open_log_file(Log),
+ Dir = get_report_dir(Options),
+ Max = get_option(Options, max, all),
+ Type = get_option(Options, type, all),
+ Abort = get_option(Options, abort_on_error, false),
+ Data = scan_files(Dir ++ "/", Max, Type),
+ {ok, #state{dir = Dir ++ "/", data = Data, device = Device,
+ max = Max, type = Type, abort = Abort, log = Log}}.
+
+handle_call({rescan, Options}, _From, State) ->
+ {Device,Log1} =
+ case get_option(Options, start_log, {undefined}) of
+ {undefined} ->
+ {State#state.device,State#state.log};
+ Log ->
+ close_device(State#state.device),
+ {open_log_file(Log),Log}
+ end,
+ Max = get_option(Options, max, State#state.max),
+ Type = get_option(Options, type, State#state.type),
+ Abort = get_option(Options, abort_on_error, false),
+ Data = scan_files(State#state.dir, Max, Type),
+ NewState = State#state{data = Data, max = Max, type = Type,
+ device = Device, abort = Abort, log = Log1},
+ {reply, ok, NewState};
+
+handle_call(_, _From, #state{data = undefined}) ->
+ {reply, {error, no_data}, #state{}};
+
+handle_call({load_list, WantedType}, _From, State) ->
+ Reports = [ {No, RealType, ShortDesc, Date}
+ || {No, RealType, ShortDesc, Date, _Fname, _FilePos}
+ <- State#state.data,
+ WantedType == all orelse RealType == WantedType ],
+ {reply, Reports, State};
+
+handle_call({load_number, Number}, _From, State) ->
+ #state{dir = Dir, data = Data, device = Device, abort = Abort, log = Log} = State,
+ Ret = load_report_by_num(Dir, Data, Number, Device, Abort, Log),
+ {reply, Ret, State#state{device = Device}}.
+
+terminate(_Reason, #state{device = Device}) ->
+ close_device(Device).
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%-----------------------------------------------------------------
+%% Func: open_log_file/1
+%% Args: FileName | standard_io
+%% Returns: A Device for later use in call to io:format
+%%-----------------------------------------------------------------
+open_log_file(standard_io) -> standard_io;
+open_log_file(FileName) ->
+ case file:open(FileName, [write,append]) of
+ {ok, Fd} -> Fd;
+ Error ->
+ io:format("rb: Cannot open file '~s' (~w).~n",
+ [FileName, Error]),
+ io:format("rb: Using standard_io~n"),
+ standard_io
+ end.
+
+close_device(Fd) when is_pid(Fd) ->
+ catch file:close(Fd);
+close_device(_) -> ok.
+
+get_option(Options, Key, Default) ->
+ case lists:keysearch(Key, 1, Options) of
+ {value, {_Key, Value}} -> Value;
+ _ -> Default
+ end.
+
+get_report_dir(Options) ->
+ case lists:keysearch(report_dir, 1, Options) of
+ {value, {_Key, RptDir}} -> RptDir;
+ _ ->
+ case catch application:get_env(sasl, error_logger_mf_dir) of
+ {ok, Dir} -> Dir;
+ _ ->
+ exit("cannot locate report directory")
+ end
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: scan_files(RptDir, Max, Type)
+%% Args: RptDir ::= string().
+%% Max ::= integer() | all, describing how many reports
+%5 to read.
+%% Type ::= atom(), describing which reports to read.
+%% Purpose: Scan all report files one time, and build a list of
+%% small elements
+%% Returns: Data, where Data is a list of
+%% {Number, Type, ShortDescr, Date, Fname, FilePosition}.
+%%-----------------------------------------------------------------
+scan_files(RptDir, Max, Type) ->
+ case file:open(RptDir ++ "/index", [raw, read]) of
+ {ok, Fd} ->
+ case catch file:read(Fd, 1) of
+ {ok, [LastWritten]} ->
+ file:close(Fd),
+ Files = make_file_list(RptDir, LastWritten),
+ scan_files(RptDir, Files, Max, Type);
+ _X ->
+ file:close(Fd),
+ exit("cannot read the index file")
+ end;
+ _X -> exit("cannot read the index file")
+ end.
+
+make_file_list(Dir, FirstFileNo) ->
+ case file:list_dir(Dir) of
+ {ok, FileNames} ->
+ FileNumbers = lists:zf(fun(Name) ->
+ case catch list_to_integer(Name) of
+ Int when is_integer(Int) ->
+ {true, Int};
+ _ ->
+ false
+ end
+ end,
+ FileNames),
+ shift(lists:sort(FileNumbers), FirstFileNo);
+ _ -> exit({bad_directory, Dir})
+ end.
+
+shift(List, First) ->
+ shift(List, First, []).
+
+shift([H | T], H, Res) ->
+ [H | Res] ++ lists:reverse(T);
+shift([H | T], First, Res) ->
+ shift(T, First, [H | Res]);
+shift([], _, Res) ->
+ Res.
+
+%%-----------------------------------------------------------------
+%% Func: scan_files(Dir, Files, Max, Type)
+%% Args: Files is a list of FileName.
+%% Purpose: Scan the report files in the index variable.
+%% Returns: {Number, Type, ShortDescr, Date, FileName, FilePosition}
+%%-----------------------------------------------------------------
+scan_files(Dir, Files, Max, Type) ->
+ scan_files(Dir, 1, Files, [], Max, Type).
+scan_files(_Dir, _, [], Res, _Max, _Type) -> Res;
+scan_files(_Dir, _, _Files, Res, Max, _Type) when Max =< 0 -> Res;
+scan_files(Dir, No, [H|T], Res, Max, Type) ->
+ Data = get_report_data_from_file(Dir, No, H, Max, Type),
+ Len = length(Data),
+ NewMax = dec_max(Max, Len),
+ NewNo = No + Len,
+ NewData = Data ++ Res,
+ scan_files(Dir, NewNo, T, NewData, NewMax, Type).
+
+dec_max(all, _) -> all;
+dec_max(X,Y) -> X-Y.
+
+get_report_data_from_file(Dir, No, FileNr, Max, Type) ->
+ Fname = integer_to_list(FileNr),
+ FileName = lists:concat([Dir, Fname]),
+ case file:open(FileName, [read]) of
+ {ok, Fd} when is_pid(Fd) -> read_reports(No, Fd, Fname, Max, Type);
+ _ -> [{No, unknown, "Can't open file " ++ Fname, "???", Fname, 0}]
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: read_reports(No, Fd, Fname, Max, Type)
+%% Purpose: Read reports from one report file.
+%% Returns: A list of {No, Type, ShortDescr, Date, FileName, FilePosition}
+%% Note: We have to read all reports, and then check the max-
+%% variable, because the reports are reversed on the file, and
+%% we may need the last ones.
+%%-----------------------------------------------------------------
+read_reports(No, Fd, Fname, Max, Type) ->
+ io:format("rb: reading report..."),
+ case catch read_reports(Fd, [], Type) of
+ {ok, Res} ->
+ file:close(Fd),
+ io:format("done.~n"),
+ NewRes =
+ if
+ length(Res) > Max ->
+ lists:sublist(Res, 1, Max);
+ true ->
+ Res
+ end,
+ add_report_data(NewRes, No, Fname);
+ {error, [Problem | Res]} ->
+ file:close(Fd),
+ io:format("Error: ~p~n",[Problem]),
+ io:format("Salvaged ~p entries from corrupt report file ~s...~n",
+ [length(Res),Fname]),
+ NewRes =
+ if
+ length([Problem|Res]) > Max ->
+ lists:sublist([Problem|Res], 1, Max);
+ true ->
+ [Problem|Res]
+ end,
+ add_report_data(NewRes, No, Fname);
+ Else ->
+ io:format("err ~p~n", [Else]),
+ [{No, unknown, "Can't read reports from file " ++ Fname,
+ "???", Fname, 0}]
+ end.
+
+read_reports(Fd, Res, Type) ->
+ {ok, FilePos} = file:position(Fd, cur),
+ case catch read_report(Fd) of
+ {ok, Report} ->
+ RealType = get_type(Report),
+ {ShortDescr, Date} = get_short_descr(Report),
+ Rep = {RealType, ShortDescr, Date, FilePos},
+ if
+ Type == all->
+ read_reports(Fd, [Rep | Res], Type);
+ RealType == Type ->
+ read_reports(Fd, [Rep | Res], Type);
+ is_list(Type) ->
+ case lists:member(RealType, Type) of
+ true ->
+ read_reports(Fd, [Rep | Res], Type);
+ _ ->
+ read_reports(Fd, Res, Type)
+ end;
+ true ->
+ read_reports(Fd, Res, Type)
+ end;
+ {error, Error} ->
+ {error, [{unknown, Error, [], FilePos} | Res]};
+ eof ->
+ {ok, Res};
+ {'EXIT', Reason} ->
+ [{unknown, Reason, [], FilePos} | Res]
+ end.
+
+read_report(Fd) ->
+ case io:get_chars(Fd,'',2) of
+ [Hi,Lo] ->
+ Size = get_int16(Hi,Lo),
+ case io:get_chars(Fd,'',Size) of
+ eof ->
+ {error,"Premature end of file"};
+ List ->
+ Bin = list_to_binary(List),
+ Ref = make_ref(),
+ case (catch {Ref,binary_to_term(Bin)}) of
+ {'EXIT',_} ->
+ {error, "Incomplete erlang term in log"};
+ {Ref,Term} ->
+ {ok, Term}
+ end
+ end;
+ eof ->
+ eof
+ end.
+
+get_int16(Hi,Lo) ->
+ ((Hi bsl 8) band 16#ff00) bor (Lo band 16#ff).
+
+%%-----------------------------------------------------------------
+%% Func: add_report_data(Res, No, FName)
+%% Args: Res is a list of {Type, ShortDescr, Date, FilePos}
+%% Purpose: Convert a list of {Type, ShortDescr, Date, FilePos} to
+%% a list of {No, Type, ShortDescr, Date, FileName, FilePos}
+%% Returns: A list of {No, Type, ShortDescr, Date, FileName, FilePos}
+%%-----------------------------------------------------------------
+add_report_data(Res, No, FName) ->
+ add_report_data(Res, No, FName, []).
+add_report_data([{Type, ShortDescr, Date, FilePos}|T], No, FName, Res) ->
+ add_report_data(T, No+1, FName,
+ [{No, Type, ShortDescr, Date, FName, FilePos}|Res]);
+add_report_data([], _No, _FName, Res) -> Res.
+
+%%-----------------------------------------------------------------
+%% Update these functions with the reports that should be possible
+%% to browse with rb.
+%%-----------------------------------------------------------------
+get_type({_Time, {error_report, _Pid, {_, crash_report, _}}}) ->
+ crash_report;
+get_type({_Time, {error_report, _Pid, {_, supervisor_report, _}}}) ->
+ supervisor_report;
+get_type({_Time, {info_report, _Pid, {_, progress, _}}}) ->
+ progress;
+get_type({_Time, {Type, _, _}}) -> Type;
+get_type(_) -> unknown.
+
+get_short_descr({{Date, Time}, {error_report, Pid, {_, crash_report, Rep}}}) ->
+ [OwnRep | _] = Rep,
+ Name =
+ case lists:keysearch(registered_name, 1, OwnRep) of
+ {value, {_Key, []}} ->
+ case lists:keysearch(initial_call, 1, OwnRep) of
+ {value, {_K, {M,_F,_A}}} -> M;
+ _ -> Pid
+ end;
+ {value, {_Key, N}} -> N;
+ _ -> Pid
+ end,
+ NameStr = lists:flatten(io_lib:format("~w", [Name])),
+ {NameStr, date_str(Date, Time)};
+get_short_descr({{Date, Time}, {error_report, Pid, {_, supervisor_report,Rep}}}) ->
+ Name =
+ case lists:keysearch(supervisor, 1, Rep) of
+ {value, {_Key, N}} when is_atom(N) -> N;
+ _ -> Pid
+ end,
+ NameStr = lists:flatten(io_lib:format("~w", [Name])),
+ {NameStr, date_str(Date,Time)};
+get_short_descr({{Date, Time}, {_Type, Pid, _}}) ->
+ NameStr = lists:flatten(io_lib:format("~w", [Pid])),
+ {NameStr, date_str(Date,Time)};
+get_short_descr(_) ->
+ {"???", "???"}.
+
+date_str({Y,Mo,D}=Date,{H,Mi,S}=Time) ->
+ case application:get_env(sasl,utc_log) of
+ {ok,true} ->
+ {{YY,MoMo,DD},{HH,MiMi,SS}} =
+ local_time_to_universal_time({Date,Time}),
+ lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~2.2.0w:"
+ "~2.2.0w:~2.2.0w UTC",
+ [YY,MoMo,DD,HH,MiMi,SS]));
+ _ ->
+ lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~2.2.0w:"
+ "~2.2.0w:~2.2.0w",
+ [Y,Mo,D,H,Mi,S]))
+ end.
+
+local_time_to_universal_time({Date,Time}) ->
+ case calendar:local_time_to_universal_time_dst({Date,Time}) of
+ [UCT] ->
+ UCT;
+ [UCT1,_UCT2] ->
+ UCT1;
+ [] -> % should not happen
+ {Date,Time}
+ end.
+
+
+load_report_by_num(Dir, Data, Number, Device, Abort, Log) ->
+ case find_report(Data, Number) of
+ {Fname, FilePosition} ->
+ FileName = lists:concat([Dir, Fname]),
+ case file:open(FileName, [read]) of
+ {ok, Fd} ->
+ case load_rep(Fd, FilePosition, Device, Abort, Log) of
+ {ok, Date, Msg} ->
+ {ok, Date, Msg};
+ Err ->
+ {error, Err}
+ end;
+ _ ->
+ Msg = io_lib:format("rb: can't open file ~p~n", [Fname]),
+ {{error, Msg},Device}
+ end;
+ no_report ->
+ {{error, no_report},Device}
+ end.
+
+find_report([{No, _Type, _Descr, _Date, Fname, FilePosition}|_T], No) ->
+ {Fname, FilePosition};
+find_report([_H|T], No) ->
+ find_report(T, No);
+find_report([], No) ->
+ io:format("There is no report with number ~p.~n", [No]),
+ no_report.
+
+load_rep(Fd, FilePosition, _Device, _Abort, _Log) ->
+ case read_rep_msg(Fd, FilePosition) of
+ {Date, Msg} -> {ok, Date, fmt_report(Msg)};
+ _ -> {error, "rb: Cannot read from file"}
+ end.
+
+read_rep_msg(Fd, FilePosition) ->
+ file:position(Fd, {bof, FilePosition}),
+ Res =
+ case catch read_report(Fd) of
+ {ok, Report} ->
+ {_ShortDescr, Date} = get_short_descr(Report),
+ {Date, Report};
+ _ -> error
+ end,
+ file:close(Fd),
+ Res.
+
+
+%% report formatring stuff
+
+fmt_report({Date, {ReportLevel, GL, {Pid, CrashType, CrashReport}}}) ->
+ [ {report_level, ReportLevel},
+ {group_leader, GL},
+ {date, Date},
+ {pid, Pid},
+ {report_type, CrashType}
+ | format_crashreport(CrashType, CrashReport)
+ ];
+
+fmt_report({Date, {ReportLevel, GL, GenericReport}}) ->
+ [ {report_level, ReportLevel},
+ {group_leader, GL},
+ {date, Date},
+ {generic_data, lists:flatten(io_lib:format("~p",[GenericReport]))}
+ ];
+
+fmt_report({Date, UnknownReport}) ->
+ [ {generic_data, lists:flatten(io_lib:format("~p",[UnknownReport]))},
+ {date, Date}
+ ].
+
+
+format_crashreport(crash_report, [OwnReport, LinkReport]) ->
+ [ {crashing_process, OwnReport},
+ {neighbours, LinkReport}
+ ];
+
+format_crashreport(supervisor_report, Data) ->
+ SuperName = get_opt(supervisor, Data),
+ ErrorContext = get_opt(errorContext, Data),
+ Reason = get_opt(reason, Data),
+ ChildInfo = get_opt(offender, Data),
+ [ {supervisor_name, SuperName},
+ {error_context, ErrorContext},
+ {reason, Reason},
+ {child_info, lists:map(fun(CI) -> transform_mfa(CI) end, ChildInfo)}
+ ];
+
+format_crashreport(_, Data) ->
+ [
+ {data, Data}
+ ].
+
+transform_mfa({mfa, Value}) -> {start_function, Value};
+transform_mfa(X) -> X.
+
+get_opt(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {_Key, Val}} -> Val;
+ _ -> undefined
+ end.
+
+
+
+
+
View
155 apps/bigwig/src/rb_format_supp.erl
@@ -0,0 +1,155 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(rb_format_supp).
+
+%% user interface
+-export([print/3]).
+
+%%-----------------------------------------------------------------
+%% This module prints error reports. Called from rb.
+%%-----------------------------------------------------------------
+
+print(Date, Report, Device) ->
+ Line = 79,
+%% Remove these comments when we can run rb in erl44!!!
+% case catch sasl_report:write_report(Device, Report) of
+% true -> ok;
+% _ ->
+ {_Time, Rep} = Report,
+ case Rep of
+ {error_report, _GL, {Pid, crash_report, CrashReport}} ->
+ Header = format_h(Line, "CRASH REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header} |
+ format_c(CrashReport)]),
+ true;
+ {error_report, _GL, {Pid, supervisor_report, SupReport}} ->
+ Header = format_h(Line, "SUPERVISOR REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header} |
+ format_s(SupReport)]),
+ true;
+ {error_report, _GL, {Pid, _Type, Report1}} ->
+ Header = format_h(Line, "ERROR REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header},
+ {data, Report1}]),
+ true;
+ {info_report, _GL, {Pid, progress, SupProgress}} ->
+ Header = format_h(Line, "PROGRESS REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header} |
+ format_p(SupProgress)]);
+ {info_report, _GL, {Pid, _Type, Report1}} ->
+ Header = format_h(Line, "INFO REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header},
+ {data, Report1}]),
+ true;
+ {warning_report, _GL, {Pid, _Type, Report1}} ->
+ Header = format_h(Line, "WARNING REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header},
+ {data, Report1}]),
+ true;
+ {error, _GL, {Pid, Format, Args}} ->
+ Header = format_h(Line, "ERROR REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header}]),
+ io:format(Device, Format, Args);
+ {info_msg, _GL, {Pid, Format, Args}} ->
+ Header = format_h(Line, "INFO REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header}]),
+ io:format(Device, Format, Args);
+ {warning_msg, _GL, {Pid, Format, Args}} ->
+ Header = format_h(Line, "WARNING REPORT", Pid, Date),
+ format_lib_supp:print_info(Device,
+ Line,
+ [{header, Header}]),
+ io:format(Device, Format, Args);
+ {Type, _GL, TypeReport} ->
+ io:format(Device, "~nInfo type <~w> ~s~n",
+ [Type, Date]),
+ io:format(Device, "~p", [TypeReport]);
+ _ ->
+ io:format("~nPrinting info of unknown type... ~s~n",
+ [Date]),
+ io:format(Device, "~p", [Report])
+% end
+ end.
+
+format_h(Line, Header, Pid, Date) ->
+ NHeader = lists:flatten(io_lib:format("~s ~w", [Header, Pid])),
+ DateLen = length(Date),
+ HeaderLen = Line - DateLen,
+ Format = lists:concat(["~-", HeaderLen, "s~", DateLen, "s"]),
+ io_lib:format(Format, [NHeader, Date]).
+
+
+%%-----------------------------------------------------------------
+%% Crash report
+%%-----------------------------------------------------------------
+format_c([OwnReport, LinkReport]) ->
+ [{items, {"Crashing process", OwnReport}},
+ format_neighbours(LinkReport)].
+
+format_neighbours([Data| Rest]) ->
+ [{newline, 1},
+ {items, {"Neighbour process", Data}} |
+ format_neighbours(Rest)];
+format_neighbours([]) -> [].
+
+%%-----------------------------------------------------------------
+%% Supervisor report
+%%-----------------------------------------------------------------
+format_s(Data) ->
+ SuperName = get_opt(supervisor, Data),
+ ErrorContext = get_opt(errorContext, Data),
+ Reason = get_opt(reason, Data),
+ ChildInfo = get_opt(offender, Data),
+ [{data, [{"Reporting supervisor", SuperName}]},
+ {newline, 1},
+ {items, {"Child process",
+ [{errorContext, ErrorContext},
+ {reason, Reason} |
+ lists:map(fun(CI) -> transform_mfa(CI) end, ChildInfo)]}}].
+
+transform_mfa({mfa, Value}) -> {start_function, Value};
+transform_mfa(X) -> X.
+
+%%-----------------------------------------------------------------
+%% Progress report
+%%-----------------------------------------------------------------
+format_p(Data) ->
+ [{data, Data}].
+
+get_opt(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {_Key, Val}} -> Val;
+ _ -> undefined
+ end.
View
161 apps/bigwig/src/reloader.erl
@@ -0,0 +1,161 @@
+%% @copyright 2007 Mochi Media, Inc.
+%% @author Matthew Dempsky <matthew@mochimedia.com>
+%%
+%% @doc Erlang module for automatically reloading modified modules
+%% during development.
+
+-module(reloader).
+-author("Matthew Dempsky <matthew@mochimedia.com>").
+
+-include_lib("kernel/include/file.hrl").
+
+-behaviour(gen_server).
+-export([start/0, start_link/0]).
+-export([stop/0]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+-export([all_changed/0]).
+-export([is_changed/1]).
+-export([reload_modules/1]).
+-record(state, {last, tref}).
+
+%% External API
+
+%% @spec start() -> ServerRet
+%% @doc Start the reloader.
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+%% @spec start_link() -> ServerRet
+%% @doc Start the reloader.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%% @spec stop() -> ok
+%% @doc Stop the reloader.
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+%% gen_server callbacks
+
+%% @spec init([]) -> {ok, State}
+%% @doc gen_server init, opens the server in an initial state.
+init([]) ->
+ {ok, TRef} = timer:send_interval(timer:seconds(1), doit),
+ {ok, #state{last = stamp(), tref = TRef}}.
+
+%% @spec handle_call(Args, From, State) -> tuple()
+%% @doc gen_server callback.
+handle_call(stop, _From, State) ->
+ {stop, shutdown, stopped, State};
+handle_call(_Req, _From, State) ->
+ {reply, {error, badrequest}, State}.
+
+%% @spec handle_cast(Cast, State) -> tuple()
+%% @doc gen_server callback.
+handle_cast(_Req, State) ->
+ {noreply, State}.
+
+%% @spec handle_info(Info, State) -> tuple()
+%% @doc gen_server callback.
+handle_info(doit, State) ->
+ Now = stamp(),
+ doit(State#state.last, Now),
+ {noreply, State#state{last = Now}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%% @spec terminate(Reason, State) -> ok
+%% @doc gen_server termination callback.
+terminate(_Reason, State) ->
+ {ok, cancel} = timer:cancel(State#state.tref),
+ ok.
+
+
+%% @spec code_change(_OldVsn, State, _Extra) -> State
+%% @doc gen_server code_change callback (trivial).
+code_change(_Vsn, State, _Extra) ->
+ {ok, State}.
+
+%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}]
+%% @doc code:purge/1 and code:load_file/1 the given list of modules in order,
+%% return the results of code:load_file/1.
+reload_modules(Modules) ->
+ [begin code:purge(M), code:load_file(M) end || M <- Modules].
+
+%% @spec all_changed() -> [atom()]
+%% @doc Return a list of beam modules that have changed.
+all_changed() ->
+ [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)].
+
+%% @spec is_changed(atom()) -> boolean()
+%% @doc true if the loaded module is a beam with a vsn attribute
+%% and does not match the on-disk beam file, returns false otherwise.
+is_changed(M) ->
+ try
+ module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M))
+ catch _:_ ->
+ false
+ end.
+
+%% Internal API
+
+module_vsn({M, Beam, _Fn}) ->
+ {ok, {M, Vsn}} = beam_lib:version(Beam),
+ Vsn;
+module_vsn(L) when is_list(L) ->
+ {_, Attrs} = lists:keyfind(attributes, 1, L),
+ {_, Vsn} = lists:keyfind(vsn, 1, Attrs),
+ Vsn.
+
+doit(From, To) ->
+ [case file:read_file_info(Filename) of
+ {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->
+ reload(Module);
+ {ok, _} ->
+ unmodified;
+ {error, enoent} ->
+ %% The Erlang compiler deletes existing .beam files if
+ %% recompiling fails. Maybe it's worth spitting out a
+ %% warning here, but I'd want to limit it to just once.
+ gone;
+ {error, Reason} ->
+ io:format("Error reading ~s's file info: ~p~n",
+ [Filename, Reason]),
+ error
+ end || {Module, Filename} <- code:all_loaded(), is_list(Filename)].
+
+reload(Module) ->
+ io:format("Reloading ~p ...", [Module]),
+ code:purge(Module),
+ case code:load_file(Module) of
+ {module, Module} ->
+ io:format(" ok.~n"),
+ case erlang:function_exported(Module, test, 0) of
+ true ->
+ io:format(" - Calling ~p:test() ...", [Module]),
+ case catch Module:test() of
+ ok ->
+ io:format(" ok.~n"),
+ reload;
+ Reason ->
+ io:format(" fail: ~p.~n", [Reason]),
+ reload_but_test_failed
+ end;
+ false ->
+ reload
+ end;
+ {error, Reason} ->
+ io:format(" fail: ~p.~n", [Reason]),
+ error
+ end.
+
+
+stamp() ->
+ erlang:localtime().
+
+%%
+%% Tests
+%%
+-include_lib("eunit/include/eunit.hrl").
+-ifdef(TEST).
+-endif.
View
138 apps/bigwig/src/user_default.erl
@@ -0,0 +1,138 @@
+-module(user_default).
+-compile(export_all).
+-import(io, [format/1]).
+
+super_restart(Sup,Mod) ->
+ io:format("Terminating ~p:~p~n", [Sup,Mod]),
+ A = supervisor:terminate_child(Sup, Mod),
+ io:format("Restarting ~p:~p~n", [Sup,Mod]),
+ B = supervisor:restart_child(Sup, Mod),
+ {A,B}.
+
+help() ->
+ shell_default:help(),
+ format("super_restart(Sup,Id) -- restart a child on a running supervisor\n"),
+ dbghelp().
+
+dbghelp() ->
+ format("dbgtc(File) -- use dbg:trace_client() to read data from File\n"),
+ format("dbgon(M) -- enable dbg tracer on all funs in module M\n"),
+ format("dbgon(M,Fun) -- enable dbg tracer for module M and function F\n"),
+ format("dbgon(M,File) -- enable dbg tracer for module M and log to File\n"),
+ format("dbgadd(M) -- enable call tracer for module M\n"),
+ format("dbgadd(M,F) -- enable call tracer for function M:F\n"),
+ format("dbgdel(M) -- disable call tracer for module M\n"),
+ format("dbgdel(M,F) -- disable call tracer for function M:F\n"),
+ format("dbgoff() -- disable dbg tracer (calls dbg:stop/0)\n"),
+ format("l() -- load all changed modules\n"),
+ format("nl() -- load all changed modules on all known nodes\n"),
+ format("mm() -- list modified modules\n"),
+ true.
+
+dbgtc(File) ->
+ Fun = fun({trace,_,call,{M,F,A}}, _) ->
+ io:format("call: ~w:~w~w~n", [M,F,A]);
+ ({trace,_,return_from,{M,F,A},R}, _) ->
+ io:format("retn: ~w:~w/~w -> ~w~n", [M,F,A,R]);
+ (A,B) ->
+ io:format("~w: ~w~n", [A,B])
+ end,
+ dbg:trace_client(file, File, {Fun, []}).
+
+dbgon(Module) ->
+ case dbg:tracer() of
+ {ok,_} ->
+ dbg:p(all,call),
+ dbg:tpl(Module, [{'_',[],[{return_trace}]}]),
+ ok;
+ Else ->
+ Else
+ end.
+
+dbgon(Module, Fun) when is_atom(Fun) ->
+ {ok,_} = dbg:tracer(),
+ dbg:p(all,call),
+ dbg:tpl(Module, Fun, [{'_',[],[{return_trace}]}]),
+ ok;
+
+dbgon(Module, File) when is_list(File) ->
+ {ok,_} = dbg:tracer(file, dbg:trace_port(file, File)),
+ dbg:p(all,call),
+ dbg:tpl(Module, [{'_',[],[{return_trace}]}]),
+ ok.
+
+dbgadd(Module) ->
+ dbg:tpl(Module, [{'_',[],[{return_trace}]}]),
+ ok.
+
+dbgadd(Module, Fun) ->
+ dbg:tpl(Module, Fun, [{'_',[],[{return_trace}]}]),
+ ok.
+
+dbgdel(Module) ->
+ dbg:ctpl(Module),
+ ok.
+
+dbgdel(Module, Fun) ->
+ dbg:ctpl(Module, Fun),
+ ok.
+
+dbgoff() ->
+ dbg:stop().
+
+
+lm() ->
+ [c:l(M) || M <- mm()].
+
+mm() ->
+ modified_modules().
+
+modified_modules() ->
+ [M || {M, _} <- code:all_loaded(), module_modified(M) == true].
+
+module_modified(Module) ->
+ case code:is_loaded(Module) of
+ {file, preloaded} ->
+ false;
+ {file, Path} ->
+ CompileOpts = proplists:get_value(compile, Module:module_info()),
+ CompileTime = proplists:get_value(time, CompileOpts),
+ Src = proplists:get_value(source, CompileOpts),
+ module_modified(Path, CompileTime, Src);
+ _ ->
+ false
+ end.
+
+module_modified(Path, PrevCompileTime, PrevSrc) ->
+ case find_module_file(Path) of
+ false ->
+ false;
+ ModPath ->
+ case beam_lib:chunks(ModPath, ["CInf"]) of
+ {ok, {_, [{_, CB}]}} ->
+ CompileOpts = binary_to_term(CB),
+ CompileTime = proplists:get_value(time, CompileOpts),
+ Src = proplists:get_value(source, CompileOpts),
+ not (CompileTime == PrevCompileTime) and (Src == PrevSrc);
+ _ ->
+ false
+ end
+ end.
+
+find_module_file(Path) ->
+ case file:read_file_info(Path) of
+ {ok, _} ->
+ Path;
+ _ ->
+ %% may be the path was changed?
+ case code:where_is_file(filename:basename(Path)) of
+ non_existing ->
+ false;
+ NewPath ->
+ NewPath
+ end
+ end.
+
+
+time(F) when is_function(F) ->
+ S = now(), Res = F(), E = now(), {timer:now_diff(E,S), Res}.
View
BIN  rebar
Binary file not shown
View
14 rebar.config
@@ -0,0 +1,14 @@
+{sub_dirs, [
+ "apps/bigwig"
+ ]}.
+{erl_opts, [debug_info]}. %, fail_on_warning]}.
+
+{require_otp_vsn, "R14"}.
+
+{deps_dir, ["deps"]}.
+
+{deps, [
+ {cowboy, ".*", {git, "https://github.com/extend/cowboy.git", "master"}},
+ {erlydtl, "0.7.0", {git, "git://github.com/RJ/erlydtl.git", "master"}}
+]}.
+
View
4 start-dev.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+cd `dirname $0`
+make
+exec erl -sname bigwig -pa $PWD/apps/*/ebin $PWD/deps/*/ebin -boot start_sasl -config sys.config -s reloader -s bigwig
View
10 sys.config
@@ -0,0 +1,10 @@
+[
+ %% SASL config
+ {sasl, [
+ {sasl_error_logger, {file, "log/sasl-error.log"}},
+ {errlog_type, error},
+ {error_logger_mf_dir, "log/sasl"}, % Log directory
+ {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size
+ {error_logger_mf_maxfiles, 5} % 5 files max
+ ]}
+].
Please sign in to comment.
Something went wrong with that request. Please try again.