ernie 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ = 2.0.0 / 2010-02-16
2
+ * Major Changes
3
+ * Use configuration file for defining handlers
4
+ * Add Native Erlang modules
5
+ * Abstract handler logic to support handlers in any language
6
+ * Add High/Low connection queues
7
+ * Remove Ruby DSL (must use Ernie.expose now)
8
+
1
9
  = 1.3.0 / 2009-11-30
2
10
  * API Additions
3
11
  * Add loglevel for setting log level
data/README.md CHANGED
@@ -3,7 +3,25 @@ Ernie
3
3
 
4
4
  By Tom Preston-Werner (tom@mojombo.com)
5
5
 
6
- Ernie is a BERT-RPC server implementation that uses an Erlang server to accept incoming connections, and then delegates the request to Ruby handlers.
6
+ Ernie is a BERT-RPC server implementation that uses an Erlang server to accept
7
+ incoming connections, and then delegates the request to custom modules that
8
+ you can write in any language (currently only Ruby and Erlang support is
9
+ included).
10
+
11
+ Modules that are written in Ruby or any non-Erlang language are known as
12
+ "external" modules and you must specify how many workers of each module should
13
+ be spawned. Requests against these modules are balanced between the workers.
14
+ Modules that are written in Erlang are known as "native" modules and run
15
+ within the Erlang server's runtime. Since these are spawned as lightweight
16
+ processes, there is no balancing necessary and much less communication
17
+ overhead when compared to external modules.
18
+
19
+ Ernie supports multiple heterogenous modules. For instance, you can have an
20
+ external Ruby module running 10 workers *and* a native Erlang module running
21
+ simultaneously. Ernie keeps track of sending requests to the proper module.
22
+ Using a technique called "shadowing," you can selectively optimize certain
23
+ external module functions with native code and Ernie will handle selecting the
24
+ correct function.
7
25
 
8
26
  See the full BERT-RPC specification at [bert-rpc.org](http://bert-rpc.org).
9
27
 
@@ -12,70 +30,173 @@ Ernie currently supports the following BERT-RPC features:
12
30
  * `call` requests
13
31
  * `cast` requests
14
32
 
15
- Ernie was developed for GitHub and is currently in production use serving millions of RPC requests every day. The stability and performance have been exemplary.
33
+ Ernie was developed for GitHub and is currently in production use serving
34
+ millions of RPC requests every day. The stability and performance have been
35
+ exemplary.
16
36
 
37
+ Ernie follows [Semantic Versioning](http://semver.org/) for release
38
+ versioning.
17
39
 
18
40
  Installation
19
41
  ------------
20
42
 
21
- You must have Erlang installed before installing Ernie.
43
+ Step 1: Install Erlang.
22
44
 
23
- $ gem install ernie -s http://gemcutter.org
45
+ http://www.erlang.org/download.html
46
+
47
+ Step 2: Install Ernie:
48
+
49
+ $ gem install ernie
24
50
 
25
51
 
26
52
  Running
27
53
  -------
28
54
 
29
55
  Usage: ernie [command] [options]
30
- -h, --handler HANDLER Handler file
31
- -p, --port PORT Port
32
- -n, --number NUMBER Number of handler instances
33
- -d, --detached Run as a daemon
56
+ -c, --config CONFIG Config file.
57
+ -p, --port PORT Port.
58
+ -l, --log-level Log level (0-4).
59
+ -d, --detached Run as a daemon.
34
60
  -P, --pidfile PIDFILE Location to write pid file.
35
61
 
36
62
  Commands:
37
63
  <none> Start an Ernie server.
38
- reload-handlers Gracefully reload all of the the ruby handlers
64
+ reload-handlers Gracefully reload all of the external handlers
39
65
  and use the new code for all subsequent requests.
40
66
  stats Print a list of connection and handler statistics.
41
67
 
42
68
  Examples:
43
- ernie -d -p 9999 -n 10 -h calc.rb
44
- Start the ernie server in the background on port 9999 with ten
45
- handlers, using the calc.rb handler file.
69
+ ernie -d -p 9999 -c example.cfg
70
+ Start the ernie server in the background on port 9999 using the
71
+ example.cfg configuration file.
46
72
 
47
73
  ernie reload-handlers -p 9999
48
74
  Reload the handlers for the ernie server currently running on
49
75
  port 9999.
50
76
 
51
- Example Handler
52
- ---------------
77
+
78
+ Configuration File
79
+ ------------------
80
+
81
+ Ernie configuration files are written as a series of dotted Erlang terms. Each
82
+ term is a list of 2-tuples that specify options for a module.
83
+
84
+ ### Native Modules
85
+
86
+ The form for native modules is:
87
+
88
+ [{module, Module},
89
+ {type, native},
90
+ {codepaths, CodePaths}].
91
+
92
+ Where Module is an atom corresponding to the module name and CodePaths is a
93
+ list of strings representing the file paths that should be added to the
94
+ runtime's code path. These paths will be prepended to the code path and must
95
+ include the native module's directory and the directories of any dependencies.
96
+
97
+ ### External Modules
98
+
99
+ The form for external modules is:
100
+
101
+ [{module, Module},
102
+ {type, external},
103
+ {command, Command},
104
+ {count, Count}].
105
+
106
+ Where Module is an atom corresponding to the module name, Command is a string
107
+ specifying the command to be executed in order to start a worker, and Count is
108
+ the number of workers to spawn.
109
+
110
+ ### Shadowing
111
+
112
+ If you specify a native module and an external module of the same name (and in
113
+ that order), Ernie will inspect the native module to see if it has the
114
+ requested function exported and use that if it does. If it does not, then it
115
+ will fall back on the external module. This can be used to selectively
116
+ optimize certain functions in a module without any modifications to your
117
+ client code.
118
+
119
+ ### Predicate Shadowing
120
+
121
+ In some circumstances it can be nice to conditionally shadow a function in an
122
+ external module based on the nature of the arguments. For example, you might
123
+ want requests for `math:fib(X)` to be routed to the external module when X is
124
+ less than 10, but to be handled by the native module when X is 10 or greater.
125
+ This can be accomplished by implementing a function `math:fib_pred(X)` in the
126
+ native module. Notice the `_pred` appended to the normal function name (pred
127
+ is short for predicate). If a function like this is present, Ernie will call
128
+ it with the requested arguments and if the return value is `true` the native
129
+ module will be used. If the return value is `false` the external module will
130
+ be used.
131
+
132
+
133
+ Example Configuration File
134
+ --------------------------
135
+
136
+ The following example config file informs Ernie of two modules. The first term
137
+ identifies a native module 'nat' that resides in the nat.beam file under the
138
+ '/path/to/app/ebin' directory. The second term specifies an external module
139
+ 'ext' that will have 2 workers started with the command 'ruby
140
+ /path/to/app/ernie/ext.rb'.
141
+
142
+ [{module, nat},
143
+ {type, native},
144
+ {codepaths, ["/path/to/app/ebin"]}].
145
+
146
+ [{module, ext},
147
+ {type, external},
148
+ {command, "ruby /path/to/app/ernie/ext.rb"},
149
+ {count, 2}].
150
+
151
+
152
+ Native (Erlang) Handler
153
+ -----------------------
154
+
155
+ Native handlers are written as normal Erlang modules. The exported functions
156
+ will become available to BERT-RPC clients.
157
+
158
+ ### Example
159
+
160
+ -module(nat).
161
+ -export([add/2]).
162
+
163
+ add(A, B) ->
164
+ A + B.
165
+
166
+ ### BERT-RPC Sequence Example
167
+
168
+ -> {call, nat, add, [1, 2]}
169
+ <- {reply, 3}
170
+
171
+
172
+ External (Ruby) Handler
173
+ -----------------------
174
+
175
+ Included in this gem is a library called `ernie` that makes it easy to write
176
+ Ernie handlers in Ruby. All you have to do is write a standard Ruby module and
177
+ expose it to Ernie and the functions of that module will become available to
178
+ BERT-RPC clients.
179
+
180
+ ### Example
53
181
 
54
182
  Using a Ruby module and Ernie.expose:
55
183
 
56
184
  require 'ernie'
57
185
 
58
- module Calc
186
+ module Ext
59
187
  def add(a, b)
60
188
  a + b
61
189
  end
62
190
  end
63
191
 
64
- Ernie.expose(:calc, Calc)
65
-
66
- Using the DSL (this will be deprecated in a future release):
192
+ Ernie.expose(:ext, Ext)
67
193
 
68
- require 'ernie'
194
+ ### BERT-RPC Sequence Example
69
195
 
70
- mod(:calc) do
71
- fun(:add) do |a, b|
72
- a + b
73
- end
74
- end
196
+ -> {call, nat, add, [1, 2]}
197
+ <- {reply, 3}
75
198
 
76
-
77
- Logging
78
- -------
199
+ ### Logging
79
200
 
80
201
  You can have logging sent to a file by adding these lines to your handler:
81
202
 
@@ -86,21 +207,31 @@ This will log startup info, requests, and error messages to the log. Choosing
86
207
  Logger::DEBUG will include the response (be careful, doing this can generate
87
208
  very large log files).
88
209
 
210
+ ### Autostart
89
211
 
90
- Autostart
91
- ---------
92
-
93
- Normally Ernie handlers will become active after the file has been loaded in.
94
- you can disable this behavior by setting:
212
+ Normally Ruby Ernie handlers will become active after the file has been loaded
213
+ in. you can disable this behavior by setting:
95
214
 
96
215
  Ernie.auto_start = false
97
216
 
98
217
 
99
- Example BERT-RPC call for above example
100
- ---------------------------------------
218
+ Selecting Queue Priority
219
+ ------------------------
220
+
221
+ Ernie maintains High and Low priority queues for incoming connections. If
222
+ there are any connections in the High priority queue, they will always be
223
+ processed first. If the High priority queue is empty, connections will be
224
+ processed from the Low priority queue. By default, connections go into the
225
+ High priority queue. To select a queue, an info BERP of the following form
226
+ must be sent preceding the call.
227
+
228
+ -- {info, priority, Priority}
101
229
 
102
- -> {call, calc, add, [1, 2]}
230
+ Where `Priority` is either the `high` or `low` atom. An example sequence where
231
+ the low priority queue is being selected would look like the following.
103
232
 
233
+ -> {info, priority, low}
234
+ -> {call, nat, add, [1, 2]}
104
235
  <- {reply, 3}
105
236
 
106
237
 
@@ -112,7 +243,7 @@ You can make BERT-RPC calls from Ruby with the [BERTRPC gem](http://github.com/m
112
243
  require 'bertrpc'
113
244
 
114
245
  svc = BERTRPC::Service.new('localhost', 8000)
115
- svc.call.calc.add(1, 2)
246
+ svc.call.ext.add(1, 2)
116
247
  # => 3
117
248
 
118
249
 
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- :minor: 3
3
- :build:
2
+ :major: 2
3
+ :minor: 0
4
4
  :patch: 0
5
- :major: 1
5
+ :build:
data/bin/ernie CHANGED
@@ -42,18 +42,14 @@ OptionParser.new do |opts|
42
42
  opts.banner = help
43
43
  opts.version = version
44
44
 
45
- opts.on("-h HANDLER", "--handler HANDLER", "Handler ruby file") do |x|
46
- options[:handler] = x
45
+ opts.on("-c CONFIG", "--config CONFIG", "Config file") do |x|
46
+ options[:config] = x
47
47
  end
48
48
 
49
49
  opts.on("-p PORT", "--port PORT", "Port") do |x|
50
50
  options[:port] = x
51
51
  end
52
52
 
53
- opts.on("-n NUMBER", "--number NUMBER", "Number of handler instances") do |x|
54
- options[:number] = x
55
- end
56
-
57
53
  opts.on("-l LOGLEVEL", "--log-level LOGLEVEL", "Log level (0-4)") do |x|
58
54
  options[:log_level] = x
59
55
  end
@@ -81,14 +77,13 @@ if command = ARGV[0]
81
77
  svc = BERTRPC::Service.new('localhost', port)
82
78
  puts svc.call.__admin__.send(command.gsub(/-/, '_'))
83
79
  else
84
- if !options[:handler]
85
- puts "A handler must be specified: ernie -h /path/to/handler.rb"
80
+ if !options[:config]
81
+ puts "A config file must be specified: ernie -c /path/to/config.yml"
86
82
  exit(1)
87
83
  end
88
84
 
89
- handler = options[:handler]
85
+ config = options[:config]
90
86
  port = options[:port] || DEFAULT_PORT
91
- number = options[:number] || 1
92
87
  log_level = options[:log_level] || 2
93
88
  pidfile = options[:pidfile] ? "-ernie_server_app pidfile \"'#{options[:pidfile]}'\"" : ''
94
89
  detached = options[:detached] ? '-detached' : ''
@@ -101,8 +96,7 @@ else
101
96
  #{code_paths}
102
97
  #{pidfile} \
103
98
  -ernie_server_app port #{port} \
104
- -ernie_server_app handler '"#{handler}"' \
105
- -ernie_server_app number #{number} \
99
+ -ernie_server_app config '"#{config}"' \
106
100
  -ernie_server_app log_level #{log_level} \
107
101
  -run ernie_server_app boot}.squeeze(' ')
108
102
  puts cmd
@@ -0,0 +1,76 @@
1
+ % erlc *.erl && erl ebench.beam -run ebench start 10000 20 ext add
2
+
3
+ -module(ebench).
4
+ -export([start/1]).
5
+
6
+ start([Ni, Ci, Modi, Funi]) ->
7
+ Nt = list_to_integer(Ni),
8
+ C = list_to_integer(Ci),
9
+ Mod = list_to_atom(Modi),
10
+ Fun = list_to_atom(Funi),
11
+ N = round(Nt / C),
12
+ T0 = erlang:now(),
13
+ Waiter = spawn(fun() -> wait(T0, N * C) end),
14
+ spawner(Waiter, N, C, Mod, Fun).
15
+
16
+ spawner(_Waiter, _N, 0, _Mod, _Fun) ->
17
+ ok;
18
+ spawner(Waiter, N, C, Mod, Fun) ->
19
+ spawn(fun() -> loop(Waiter, N, Mod, Fun) end),
20
+ spawner(Waiter, N, C - 1, Mod, Fun).
21
+
22
+ % X is the total number of responses to wait for
23
+ wait(T0, XTotal, 0) ->
24
+ T1 = erlang:now(),
25
+ Diff = timer:now_diff(T1, T0),
26
+ Mean = Diff / XTotal,
27
+ io:format("~p requests completed in ~.2fs~n", [XTotal, Diff / 1000000]),
28
+ io:format("Mean request time: ~.2fms (~.2f r/s)~n", [Mean / 1000, XTotal / (Diff / 1000000)]),
29
+ init:stop();
30
+ wait(T0, XTotal, X) ->
31
+ receive
32
+ done -> wait(T0, XTotal, X - 1)
33
+ end.
34
+
35
+ wait(T0, X) ->
36
+ wait(T0, X, X).
37
+
38
+ loop(_Waiter, 0, _Mod, _Fun) ->
39
+ ok;
40
+ loop(Waiter, N, Mod, Fun) ->
41
+ hit(Waiter, Mod, Fun),
42
+ loop(Waiter, N - 1, Mod, Fun).
43
+
44
+ hit(Waiter, Mod, Fun) ->
45
+ % io:format("outgoing!~n", []),
46
+ Host = "localhost",
47
+ case gen_tcp:connect(Host, 8000, [binary, {packet, 4}]) of
48
+ {ok, Sock} -> process(Waiter, Mod, Fun, Sock);
49
+ Any ->
50
+ io:format("Unable to establish connection: ~p~n", [Any]),
51
+ Waiter ! done
52
+ end.
53
+
54
+ process(Waiter, Mod, Fun, Sock) ->
55
+ % Info = term_to_binary({info, priority, [low]}),
56
+ % ok = gen_tcp:send(Sock, Info),
57
+ Request = term_to_binary({call, Mod, Fun, args(Fun)}),
58
+ ok = gen_tcp:send(Sock, Request),
59
+ receive
60
+ {tcp, _Port, Reply} ->
61
+ % io:format("~p~n", [Reply]),
62
+ Res = res(Fun),
63
+ {reply, Res} = binary_to_term(Reply);
64
+ {tcp_closed, Port} ->
65
+ io:format("Connection closed after sending data: ~p~n", [Port]);
66
+ Any ->
67
+ io:format("Unexpected message: ~p~n", [Any])
68
+ end,
69
+ Waiter ! done,
70
+ ok = gen_tcp:close(Sock).
71
+
72
+ args(add) -> [1, 2];
73
+ args(fib) -> [20].
74
+
75
+ res(add) -> 3;
76
+ res(fib) -> 10946.
data/elib/asset_pool.erl CHANGED
@@ -2,7 +2,7 @@
2
2
  -behaviour(gen_server).
3
3
 
4
4
  %% api
5
- -export([start_link/1, start/1, lease/0, return/1, reload_assets/0, idle_worker_count/0]).
5
+ -export([start_link/2, lease/1, return/2, reload_assets/1, idle_worker_count/1]).
6
6
 
7
7
  %% gen_server callbacks
8
8
  -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -16,23 +16,20 @@
16
16
  %% API
17
17
  %%====================================================================
18
18
 
19
- start_link(Args) ->
20
- gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
19
+ start_link(Handler, Count) ->
20
+ gen_server:start_link(?MODULE, [Handler, Count], []).
21
21
 
22
- start(Args) ->
23
- gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
22
+ lease(Pid) ->
23
+ gen_server:call(Pid, lease).
24
24
 
25
- lease() ->
26
- gen_server:call({global, ?MODULE}, {lease}).
25
+ return(Pid, Asset) ->
26
+ gen_server:call(Pid, {return, Asset}).
27
27
 
28
- return(Asset) ->
29
- gen_server:call({global, ?MODULE}, {return, Asset}).
28
+ reload_assets(Pid) ->
29
+ gen_server:call(Pid, reload_assets).
30
30
 
31
- reload_assets() ->
32
- gen_server:call({global, ?MODULE}, {reload_assets}).
33
-
34
- idle_worker_count() ->
35
- gen_server:call({global, ?MODULE}, {idle_worker_count}).
31
+ idle_worker_count(Pid) ->
32
+ gen_server:call(Pid, idle_worker_count).
36
33
 
37
34
  %%====================================================================
38
35
  %% gen_server callbacks
@@ -45,11 +42,12 @@ idle_worker_count() ->
45
42
  %% {stop, Reason}
46
43
  %% Description: Initiates the server
47
44
  %%--------------------------------------------------------------------
48
- init([Count, Handler]) ->
45
+ init([Handler, Count]) ->
49
46
  process_flag(trap_exit, true),
50
47
  error_logger:info_msg("~p starting~n", [?MODULE]),
51
48
  Token = make_ref(),
52
49
  Assets = start_handlers(Count, Handler, Token),
50
+ logger:debug("Assets = ~p~n", [Assets]),
53
51
  {ok, #state{assets = Assets, handler = Handler, token = Token}}.
54
52
 
55
53
  %%--------------------------------------------------------------------
@@ -61,7 +59,8 @@ init([Count, Handler]) ->
61
59
  %% {stop, Reason, State}
62
60
  %% Description: Handling call messages
63
61
  %%--------------------------------------------------------------------
64
- handle_call({lease}, _From, State) ->
62
+ handle_call(lease, _From, State) ->
63
+ logger:debug("Leasing...~n", []),
65
64
  Token = State#state.token,
66
65
  case queue:out(State#state.assets) of
67
66
  {{value, Asset}, Assets2} ->
@@ -91,10 +90,10 @@ handle_call({return, Asset}, _From, State) ->
91
90
  end,
92
91
  Assets2 = queue:in(NewAsset, State#state.assets),
93
92
  {reply, ok, State#state{assets = Assets2}};
94
- handle_call({reload_assets}, _From, State) ->
93
+ handle_call(reload_assets, _From, State) ->
95
94
  Token = make_ref(),
96
95
  {reply, ok, State#state{token = Token}};
97
- handle_call({idle_worker_count}, _From, State) ->
96
+ handle_call(idle_worker_count, _From, State) ->
98
97
  WorkerCount = queue:len(State#state.assets),
99
98
  {reply, WorkerCount, State};
100
99
  handle_call(_Request, _From, State) ->
@@ -140,4 +139,4 @@ start_handlers(Assets, Count, Handler, Token) ->
140
139
  start_handlers(Assets2, Count - 1, Handler, Token).
141
140
 
142
141
  create_asset(Handler, Token) ->
143
- {asset, port_wrapper:wrap_link("ruby " ++ Handler), Token}.
142
+ {asset, port_wrapper:wrap_link(Handler), Token}.
@@ -1,15 +1,13 @@
1
1
  -module(asset_pool_sup).
2
2
  -behaviour(supervisor).
3
- -export([start_link/0, init/1]).
3
+ -export([start_link/2, init/1]).
4
4
 
5
- start_link() ->
6
- supervisor:start_link({local, ?MODULE}, ?MODULE, []).
5
+ start_link(Handler, Number) ->
6
+ supervisor:start_link(?MODULE, [Handler, Number]).
7
7
 
8
- init([]) ->
9
- {ok, Handler} = application:get_env(ernie_server_app, handler),
8
+ init([Handler, Number]) ->
10
9
  io:format("Using handler ~p~n", [Handler]),
11
- {ok, Number} = application:get_env(ernie_server_app, number),
12
10
  io:format("Using ~p handler instances~n", [Number]),
13
11
  {ok, {{one_for_one, 1, 60},
14
- [{asset_pool, {asset_pool, start_link, [[Number, Handler]]},
12
+ [{asset_pool, {asset_pool, start_link, [Handler, Number]},
15
13
  permanent, brutal_kill, worker, [asset_pool]}]}}.
data/elib/bert.erl ADDED
@@ -0,0 +1,69 @@
1
+ %%% See http://github.com/mojombo/bert.erl for documentation.
2
+ %%% MIT License - Copyright (c) 2009 Tom Preston-Werner <tom@mojombo.com>
3
+
4
+ -module(bert).
5
+ -version('1.1.0').
6
+ -author("Tom Preston-Werner").
7
+
8
+ -export([encode/1, decode/1]).
9
+
10
+ -ifdef(TEST).
11
+ -include("test/bert_test.erl").
12
+ -endif.
13
+
14
+ %%---------------------------------------------------------------------------
15
+ %% Public API
16
+
17
+ -spec encode(term()) -> binary().
18
+
19
+ encode(Term) ->
20
+ term_to_binary(encode_term(Term)).
21
+
22
+ -spec decode(binary()) -> term().
23
+
24
+ decode(Bin) ->
25
+ decode_term(binary_to_term(Bin)).
26
+
27
+ %%---------------------------------------------------------------------------
28
+ %% Encode
29
+
30
+ -spec encode_term(term()) -> term().
31
+
32
+ encode_term(Term) ->
33
+ case Term of
34
+ [] -> {bert, nil};
35
+ true -> {bert, true};
36
+ false -> {bert, false};
37
+ Dict when is_record(Term, dict, 8) ->
38
+ {bert, dict, dict:to_list(Dict)};
39
+ List when is_list(Term) ->
40
+ lists:map((fun encode_term/1), List);
41
+ Tuple when is_tuple(Term) ->
42
+ TList = tuple_to_list(Tuple),
43
+ TList2 = lists:map((fun encode_term/1), TList),
44
+ list_to_tuple(TList2);
45
+ _Else -> Term
46
+ end.
47
+
48
+ %%---------------------------------------------------------------------------
49
+ %% Decode
50
+
51
+ -spec decode_term(term()) -> term().
52
+
53
+ decode_term(Term) ->
54
+ case Term of
55
+ {bert, nil} -> [];
56
+ {bert, true} -> true;
57
+ {bert, false} -> false;
58
+ {bert, dict, Dict} ->
59
+ dict:from_list(Dict);
60
+ {bert, Other} ->
61
+ {bert, Other};
62
+ List when is_list(Term) ->
63
+ lists:map((fun decode_term/1), List);
64
+ Tuple when is_tuple(Term) ->
65
+ TList = tuple_to_list(Tuple),
66
+ TList2 = lists:map((fun decode_term/1), TList),
67
+ list_to_tuple(TList2);
68
+ _Else -> Term
69
+ end.
data/elib/ernie.hrl ADDED
@@ -0,0 +1,10 @@
1
+ -record(state, {lsock = undefined, % the listen socket
2
+ hq = queue:new(), % high priority queue
3
+ lq = queue:new(), % low priority queue
4
+ count = 0, % total request count
5
+ map = undefined}). % module map. tuples of {Mod, Id}
6
+
7
+ -record(request, {sock = undefined, % connection socket
8
+ infos = [], % list of info binaries
9
+ action = undefined, % action binary
10
+ priority = high}). % priority [ high | low ]
@@ -0,0 +1,60 @@
1
+ -module(ernie_admin).
2
+ -export([process/4]).
3
+ -include_lib("ernie.hrl").
4
+
5
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6
+ % Process entry point
7
+
8
+ process(Sock, reload_handlers, _Args, State) ->
9
+ spawn(fun() -> process_reload_assets(Sock, State) end),
10
+ State;
11
+ process(Sock, stats, _Args, State) ->
12
+ spawn(fun() -> process_stats(Sock, State) end),
13
+ State;
14
+ process(Sock, _Fun, _Args, State) ->
15
+ gen_tcp:send(Sock, term_to_binary({reply, <<"Admin function not supported.">>})),
16
+ ok = gen_tcp:close(Sock),
17
+ State.
18
+
19
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
20
+ % Reload handlers
21
+
22
+ process_reload_assets(Sock, State) ->
23
+ lists:map((fun reload/1), State#state.map),
24
+ gen_tcp:send(Sock, term_to_binary({reply, <<"Handlers reloaded.">>})),
25
+ ok = gen_tcp:close(Sock).
26
+
27
+ reload({_Mod, native}) ->
28
+ ok;
29
+ reload({_Mod, Pid}) ->
30
+ asset_pool:reload_assets(Pid).
31
+
32
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33
+ % Stats
34
+
35
+ process_stats(Sock, State) ->
36
+ CountString = stat(count, State),
37
+ IdleWorkersString = stat(idle, State),
38
+ QueueLengthString = stat(queue, State),
39
+ StatString = list_to_binary([CountString, IdleWorkersString, QueueLengthString]),
40
+ Data = term_to_binary({reply, StatString}),
41
+ gen_tcp:send(Sock, Data),
42
+ ok = gen_tcp:close(Sock).
43
+
44
+ stat(count, State) ->
45
+ Count = State#state.count,
46
+ list_to_binary([<<"connections.total=">>, integer_to_list(Count), <<"\n">>]);
47
+ stat(idle, State) ->
48
+ IdleMap = lists:map((fun idle/1), State#state.map),
49
+ list_to_binary(IdleMap);
50
+ stat(queue, State) ->
51
+ HighQueueLength = queue:len(State#state.hq),
52
+ LowQueueLength = queue:len(State#state.lq),
53
+ list_to_binary([<<"queue.high=">>, integer_to_list(HighQueueLength), <<"\n">>,
54
+ <<"queue.low=">>, integer_to_list(LowQueueLength), <<"\n">>]).
55
+
56
+ idle({Mod, native}) ->
57
+ list_to_binary([<<"workers.idle.">>, atom_to_list(Mod), <<"=native\n">>]);
58
+ idle({Mod, Pid}) ->
59
+ IdleCount = integer_to_list(asset_pool:idle_worker_count(Pid)),
60
+ list_to_binary([<<"workers.idle.">>, atom_to_list(Mod), <<"=">>, IdleCount, <<"\n">>]).