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 +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">>]).
|