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 +8 -0
- data/README.md +167 -36
- data/VERSION.yml +3 -3
- data/bin/ernie +6 -12
- data/contrib/ebench.erl +76 -0
- data/elib/asset_pool.erl +18 -19
- data/elib/asset_pool_sup.erl +5 -7
- data/elib/bert.erl +69 -0
- data/elib/ernie.hrl +10 -0
- data/elib/ernie_admin.erl +60 -0
- data/elib/ernie_config.erl +30 -0
- data/elib/ernie_native.erl +22 -0
- data/elib/ernie_server.erl +139 -74
- data/elib/ernie_server_app.erl +1 -2
- data/elib/ernie_server_sup.erl +4 -1
- data/ernie.gemspec +17 -9
- data/examples/example.cfg +12 -0
- data/examples/ext.erl +8 -0
- data/examples/{expose.rb → ext.rb} +14 -2
- data/examples/nat.erl +12 -0
- data/lib/ernie.rb +1 -9
- data/test/ernie_server_test.rb +11 -11
- data/test/ernie_test.rb +0 -44
- data/test/sample/ext.rb +42 -0
- data/test/sample/sample.cfg +4 -0
- metadata +16 -8
- data/examples/dsl.rb +0 -28
- data/test/handler.rb +0 -41
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
|
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
|
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
|
-
|
43
|
+
Step 1: Install Erlang.
|
22
44
|
|
23
|
-
|
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
|
-
-
|
31
|
-
-p, --port PORT Port
|
32
|
-
-
|
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
|
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 -
|
44
|
-
Start the ernie server in the background on port 9999
|
45
|
-
|
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
|
-
|
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
|
186
|
+
module Ext
|
59
187
|
def add(a, b)
|
60
188
|
a + b
|
61
189
|
end
|
62
190
|
end
|
63
191
|
|
64
|
-
Ernie.expose(:
|
65
|
-
|
66
|
-
Using the DSL (this will be deprecated in a future release):
|
192
|
+
Ernie.expose(:ext, Ext)
|
67
193
|
|
68
|
-
|
194
|
+
### BERT-RPC Sequence Example
|
69
195
|
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
246
|
+
svc.call.ext.add(1, 2)
|
116
247
|
# => 3
|
117
248
|
|
118
249
|
|
data/VERSION.yml
CHANGED
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("-
|
46
|
-
options[:
|
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[:
|
85
|
-
puts "A
|
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
|
-
|
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
|
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
|
data/contrib/ebench.erl
ADDED
@@ -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/
|
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(
|
20
|
-
gen_server:start_link(
|
19
|
+
start_link(Handler, Count) ->
|
20
|
+
gen_server:start_link(?MODULE, [Handler, Count], []).
|
21
21
|
|
22
|
-
|
23
|
-
gen_server:
|
22
|
+
lease(Pid) ->
|
23
|
+
gen_server:call(Pid, lease).
|
24
24
|
|
25
|
-
|
26
|
-
gen_server:call(
|
25
|
+
return(Pid, Asset) ->
|
26
|
+
gen_server:call(Pid, {return, Asset}).
|
27
27
|
|
28
|
-
|
29
|
-
gen_server:call(
|
28
|
+
reload_assets(Pid) ->
|
29
|
+
gen_server:call(Pid, reload_assets).
|
30
30
|
|
31
|
-
|
32
|
-
gen_server:call(
|
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([
|
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(
|
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(
|
93
|
+
handle_call(reload_assets, _From, State) ->
|
95
94
|
Token = make_ref(),
|
96
95
|
{reply, ok, State#state{token = Token}};
|
97
|
-
handle_call(
|
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(
|
142
|
+
{asset, port_wrapper:wrap_link(Handler), Token}.
|
data/elib/asset_pool_sup.erl
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
-module(asset_pool_sup).
|
2
2
|
-behaviour(supervisor).
|
3
|
-
-export([start_link/
|
3
|
+
-export([start_link/2, init/1]).
|
4
4
|
|
5
|
-
start_link() ->
|
6
|
-
supervisor:start_link(
|
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, [
|
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">>]).
|