ernie 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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">>]).