auser-poolparty 0.2.53 → 0.2.54
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/Manifest.txt +15 -1
- data/README.txt +20 -4
- data/bin/cloud-ensure-provisioning +13 -7
- data/bin/cloud-handle-load +1 -0
- data/bin/cloud-start +1 -1
- data/bin/messenger-get-current-nodes +13 -0
- data/bin/server-show-stats +1 -1
- data/bin/server-start-client +15 -8
- data/bin/server-start-master +11 -7
- data/bin/server-start-node +12 -10
- data/bin/server-stop-client +3 -0
- data/bin/server-stop-master +3 -0
- data/bin/server-stop-node +3 -0
- data/lib/erlang/messenger/include/defines.hrl +1 -0
- data/lib/erlang/messenger/pm_client_rel-0.1.script +1 -1
- data/lib/erlang/messenger/pm_master_rel-0.1.script +1 -1
- data/lib/erlang/messenger/pm_node_rel-0.1.script +1 -1
- data/lib/erlang/messenger/src/client_server.erl +22 -8
- data/lib/erlang/messenger/src/pm_master.erl +7 -9
- data/lib/erlang/messenger/src/pm_node.erl +0 -1
- data/lib/erlang/messenger/src/pm_strings.erl +11 -0
- data/lib/erlang/messenger/src/utils.erl +12 -4
- data/lib/erlang/messenger/useful_snippets +1 -1
- data/lib/poolparty.rb +10 -2
- data/lib/poolparty/base_packages/poolparty.rb +23 -16
- data/lib/poolparty/base_packages/runit.rb +21 -0
- data/lib/poolparty/helpers/binary.rb +0 -1
- data/lib/poolparty/helpers/provisioner_base.rb +4 -0
- data/lib/poolparty/helpers/provisioners/master.rb +15 -8
- data/lib/poolparty/modules/file_writer.rb +10 -2
- data/lib/poolparty/net/messenger.rb +9 -3
- data/lib/poolparty/net/remoter.rb +12 -8
- data/lib/poolparty/plugins/runit.rb +96 -0
- data/lib/poolparty/pool/cloud.rb +1 -1
- data/lib/poolparty/pool/resource.rb +2 -1
- data/lib/poolparty/pool/resources/custom_service.rb +30 -0
- data/lib/poolparty/pool/resources/package.rb +5 -2
- data/lib/poolparty/templates/messenger/client/log-run.erb +2 -0
- data/lib/poolparty/templates/messenger/client/run.erb +4 -0
- data/lib/poolparty/templates/messenger/master/log-run.erb +2 -0
- data/lib/poolparty/templates/messenger/master/run.erb +4 -0
- data/lib/poolparty/templates/messenger/node/log-run.erb +2 -0
- data/lib/poolparty/templates/messenger/node/run.erb +4 -0
- data/lib/poolparty/version.rb +1 -1
- data/poolparty.gemspec +58 -504
- data/spec/poolparty/net/remote_spec.rb +1 -1
- metadata +21 -7
data/Manifest.txt
CHANGED
@@ -21,6 +21,7 @@ bin/cloud-ssh
|
|
21
21
|
bin/cloud-start
|
22
22
|
bin/cloud-stats
|
23
23
|
bin/cloud-terminate
|
24
|
+
bin/messenger-get-current-nodes
|
24
25
|
bin/pool
|
25
26
|
bin/pool-console
|
26
27
|
bin/pool-describe
|
@@ -37,6 +38,9 @@ bin/server-show-stats
|
|
37
38
|
bin/server-start-client
|
38
39
|
bin/server-start-master
|
39
40
|
bin/server-start-node
|
41
|
+
bin/server-stop-client
|
42
|
+
bin/server-stop-master
|
43
|
+
bin/server-stop-node
|
40
44
|
config/hoe.rb
|
41
45
|
config/requirements.rb
|
42
46
|
examples/basic.rb
|
@@ -74,8 +78,8 @@ lib/erlang/messenger/ebin/pm_node.beam
|
|
74
78
|
lib/erlang/messenger/ebin/pm_node_rel-0.1.rel
|
75
79
|
lib/erlang/messenger/ebin/pm_node_supervisor.beam
|
76
80
|
lib/erlang/messenger/ebin/pm_packager.beam
|
81
|
+
lib/erlang/messenger/ebin/pm_strings.beam
|
77
82
|
lib/erlang/messenger/ebin/utils.beam
|
78
|
-
lib/erlang/messenger/erl_crash.dump
|
79
83
|
lib/erlang/messenger/include/defines.hrl
|
80
84
|
lib/erlang/messenger/lib/eunit/AUTHORS
|
81
85
|
lib/erlang/messenger/lib/eunit/CHANGELOG
|
@@ -154,6 +158,7 @@ lib/erlang/messenger/src/pm_master_supervisor.erl
|
|
154
158
|
lib/erlang/messenger/src/pm_node.erl
|
155
159
|
lib/erlang/messenger/src/pm_node_supervisor.erl
|
156
160
|
lib/erlang/messenger/src/pm_packager.erl
|
161
|
+
lib/erlang/messenger/src/pm_strings.erl
|
157
162
|
lib/erlang/messenger/src/utils.erl
|
158
163
|
lib/erlang/messenger/useful_snippets
|
159
164
|
lib/poolparty.rb
|
@@ -162,6 +167,7 @@ lib/poolparty/base_packages/haproxy.rb
|
|
162
167
|
lib/poolparty/base_packages/heartbeat.rb
|
163
168
|
lib/poolparty/base_packages/poolparty.rb
|
164
169
|
lib/poolparty/base_packages/ruby.rb
|
170
|
+
lib/poolparty/base_packages/runit.rb
|
165
171
|
lib/poolparty/config/allowed_commands.yml
|
166
172
|
lib/poolparty/config/postlaunchmessage.txt
|
167
173
|
lib/poolparty/core/array.rb
|
@@ -220,6 +226,7 @@ lib/poolparty/net/remoter.rb
|
|
220
226
|
lib/poolparty/net/remoter_base.rb
|
221
227
|
lib/poolparty/plugins/git.rb
|
222
228
|
lib/poolparty/plugins/line.rb
|
229
|
+
lib/poolparty/plugins/runit.rb
|
223
230
|
lib/poolparty/plugins/svn.rb
|
224
231
|
lib/poolparty/pool/base.rb
|
225
232
|
lib/poolparty/pool/cloud.rb
|
@@ -232,6 +239,7 @@ lib/poolparty/pool/resource.rb
|
|
232
239
|
lib/poolparty/pool/resources/class_package.rb
|
233
240
|
lib/poolparty/pool/resources/conditional.rb
|
234
241
|
lib/poolparty/pool/resources/cron.rb
|
242
|
+
lib/poolparty/pool/resources/custom_service.rb
|
235
243
|
lib/poolparty/pool/resources/directory.rb
|
236
244
|
lib/poolparty/pool/resources/exec.rb
|
237
245
|
lib/poolparty/pool/resources/file.rb
|
@@ -251,6 +259,12 @@ lib/poolparty/templates/gem
|
|
251
259
|
lib/poolparty/templates/ha.cf
|
252
260
|
lib/poolparty/templates/haproxy.conf
|
253
261
|
lib/poolparty/templates/haresources
|
262
|
+
lib/poolparty/templates/messenger/client/log-run.erb
|
263
|
+
lib/poolparty/templates/messenger/client/run.erb
|
264
|
+
lib/poolparty/templates/messenger/master/log-run.erb
|
265
|
+
lib/poolparty/templates/messenger/master/run.erb
|
266
|
+
lib/poolparty/templates/messenger/node/log-run.erb
|
267
|
+
lib/poolparty/templates/messenger/node/run.erb
|
254
268
|
lib/poolparty/templates/namespaceauth.conf
|
255
269
|
lib/poolparty/templates/poolparty.monitor
|
256
270
|
lib/poolparty/templates/puppet.conf
|
data/README.txt
CHANGED
@@ -4,18 +4,30 @@ http://poolpartyrb.com
|
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
7
|
-
|
7
|
+
PoolParty makes it easy and simple to configure any cloud of computers. In clear language, describe your cloud
|
8
|
+
with language such as:
|
9
|
+
|
10
|
+
pool :cloud do
|
11
|
+
cloud :app do
|
12
|
+
apache do
|
13
|
+
has_virtualhost(:name => "/var/www/sites/poolpartyrb.com")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
8
17
|
|
9
18
|
== FEATURES/PROBLEMS:
|
10
19
|
|
11
|
-
|
20
|
+
* Written in Ruby and Erlang
|
21
|
+
* Written from the ground up to be extensible with plugins
|
22
|
+
* Easy git-style commands to communicate with your clouds
|
23
|
+
* Much much more
|
12
24
|
|
13
25
|
== SYNOPSIS:
|
14
26
|
|
15
|
-
|
27
|
+
PoolParty is written with the intention of being as application-agnostic as possible. It installs only the basic
|
16
28
|
required software to glue the cloud together on the instances as listed below.
|
17
29
|
|
18
|
-
|
30
|
+
PoolParty is easily configuration. In fact, it makes little assumptions about your development environment and allows
|
19
31
|
several options on how to begin configuring the cloud.
|
20
32
|
|
21
33
|
== REQUIREMENTS:
|
@@ -26,6 +38,10 @@ COMING SOON
|
|
26
38
|
|
27
39
|
sudo gem install auser-poolparty
|
28
40
|
|
41
|
+
== TODO:
|
42
|
+
* Replace services with Runit
|
43
|
+
* Refactor provisioning to use erlang
|
44
|
+
|
29
45
|
== LICENSE:
|
30
46
|
|
31
47
|
(The MIT License)
|
@@ -8,17 +8,23 @@ o = PoolParty::Optioner.new(ARGV) do |opts, optioner|
|
|
8
8
|
opts.on('-n name', '--name name', 'Host name') { |h| optioner.hostname h }
|
9
9
|
opts.on('-l', '--no-shell', 'No shell') {optioner.noshell true}
|
10
10
|
end
|
11
|
-
@hostname = o.hostname ? o.hostname : `hostname`.chomp
|
12
|
-
@hostname = "node0" if @hostname == "master" # Quick fix to make sure we have a node running on the master as well
|
13
11
|
|
14
12
|
o.loaded_clouds.each do |cloud|
|
15
13
|
|
16
|
-
with_cloud(cloud
|
14
|
+
with_cloud(cloud) do
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
@nonprovisioned_nodes = list_of_running_instances.map {|a| a.name } - cloud.get_current_nodes
|
17
|
+
# @tp = ThreadPool.new(10)
|
18
|
+
@nonprovisioned_nodes.each do |node|
|
19
|
+
vputs "Provisioning #{node}"
|
20
|
+
next if node == "master"
|
21
|
+
# @tp.process do
|
22
|
+
PoolParty::Provisioner.process_clean_reconfigure_for!(node, cloud)
|
23
|
+
`cloud-provision -i #{node.gsub(/node/, '')}`
|
24
|
+
PoolParty::Provisioner.process_clean_reconfigure_for!(node, cloud)
|
25
|
+
# end
|
26
|
+
end
|
27
|
+
# @tp.join
|
22
28
|
end
|
23
29
|
|
24
30
|
end
|
data/bin/cloud-handle-load
CHANGED
@@ -11,6 +11,7 @@ o.loaded_clouds.each do |cloud|
|
|
11
11
|
|
12
12
|
with_cloud(cloud) do
|
13
13
|
vputs header("Load handling cloud #{name}")
|
14
|
+
vputs "should_expand_cloud: #{should_contract_cloud?}"
|
14
15
|
if should_expand_cloud?
|
15
16
|
vputs "Expanding cloud based on load"
|
16
17
|
logger.debug "Expanding cloud based on load"
|
data/bin/cloud-start
CHANGED
@@ -22,7 +22,7 @@ o.loaded_clouds.each do |cloud|
|
|
22
22
|
provisioning_complete
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
puts open(::File.join(File.dirname(__FILE__), "..", "lib", "poolparty", "config", "postlaunchmessage.txt")).read ^ {:master_ip => master.ip.chomp}
|
26
26
|
clear_base_directory unless testing
|
27
27
|
end
|
28
28
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
require "poolparty"
|
4
|
+
require "poolpartycl"
|
5
|
+
|
6
|
+
o = PoolParty::Optioner.new(ARGV) do |opts, optioner|
|
7
|
+
end
|
8
|
+
|
9
|
+
o.loaded_clouds.each do |cloud|
|
10
|
+
# @nodes = cloud.messenger_send!("get_current_nodes")
|
11
|
+
# @nodes = @nodes.split(" ").map {|a| a.split(/@/)[-1] }
|
12
|
+
puts cloud.get_current_nodes.join(" ")
|
13
|
+
end
|
data/bin/server-show-stats
CHANGED
data/bin/server-start-client
CHANGED
@@ -10,13 +10,20 @@ end
|
|
10
10
|
o.loaded_clouds.each do |cloud|
|
11
11
|
|
12
12
|
with_cloud(cloud) do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
|
14
|
+
# ruby /var/lib/gems/1.8/bin/server-start-client
|
15
|
+
already_running = %x[ps aux | grep beam | grep -v grep | grep client]
|
16
|
+
if already_running.chomp.empty?
|
17
|
+
|
18
|
+
boot_file = "#{Messenger.append_dir}/pm_client_rel-0.1"
|
19
|
+
|
20
|
+
Kernel.system "cd #{Messenger.append_dir} && rake build_boot_scripts" unless ::File.file?("#{boot_file}.boot") || testing
|
21
|
+
command = Messenger.erl_command("client", "-boot #{boot_file} #{noshell ? "" : "-detached -heart -noshell"}", 7049, 7050)
|
22
|
+
vputs "Running #{command}"
|
23
|
+
|
24
|
+
Kernel.system "export HOME=/root && #{command}" unless testing
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
|
-
end
|
28
|
+
end
|
29
|
+
|
data/bin/server-start-master
CHANGED
@@ -10,13 +10,17 @@ end
|
|
10
10
|
o.loaded_clouds.each do |cloud|
|
11
11
|
|
12
12
|
with_cloud(cloud) do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
|
14
|
+
already_running = %x[ps aux | grep beam | grep -v grep | grep master]
|
15
|
+
if already_running.chomp.empty?
|
16
|
+
|
17
|
+
boot_file = "#{Messenger.append_dir}/pm_master_rel-0.1"
|
18
|
+
Kernel.system ". /etc/profile && server-build-messenger" unless ::File.file?("#{boot_file}.boot") || testing
|
19
|
+
command = Messenger.erl_command("master", "-boot #{boot_file} #{noshell ? "" : "-noshell -detached -heart"}")
|
20
|
+
vputs "Running #{command}"
|
21
|
+
|
22
|
+
Kernel.system "export HOME=/root && #{command}" unless testing
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
end
|
data/bin/server-start-node
CHANGED
@@ -15,16 +15,18 @@ available_monitors = PoolParty::Monitors.available_monitors
|
|
15
15
|
|
16
16
|
o.loaded_clouds.each do |cloud|
|
17
17
|
|
18
|
-
with_cloud(cloud) do
|
19
|
-
# TODO: Change this to be app specfic
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
with_cloud(cloud, {:hostname => @hostname}) do
|
19
|
+
# TODO: Change this to be app specfic
|
20
|
+
already_running = %x[ps aux | grep beam | grep -v grep | grep node]
|
21
|
+
if already_running.chomp.empty?
|
22
|
+
boot_file = "#{Messenger.append_dir}/pm_node_rel-0.1"
|
23
|
+
|
24
|
+
Kernel.system ". /etc/profile && server-build-messenger" unless ::File.file?("#{boot_file}.boot") || testing
|
25
|
+
command = Messenger.erl_command(hostname, "-boot #{boot_file} #{noshell ? "" : "-noshell -detached -heart"} -- #{available_monitors.join(" ")}")
|
26
|
+
vputs "Running #{command}"
|
27
|
+
|
28
|
+
Kernel.system "export HOME=/root && #{command}" unless testing
|
29
|
+
end
|
28
30
|
end
|
29
31
|
|
30
32
|
end
|
@@ -5,34 +5,48 @@
|
|
5
5
|
|
6
6
|
-define (RECONNECT_TIMEOUT, 10000).
|
7
7
|
|
8
|
-
start() ->
|
9
|
-
|
10
|
-
connect_to_master(),
|
11
|
-
global:sync(),
|
8
|
+
start() ->
|
9
|
+
utils:start_timer(client_timer, ?UPDATE_TIME, fun() -> client_server:connect_to_master() end),
|
12
10
|
pm_client:start(?MODULE, 7050, {?MODULE, loop}).
|
13
11
|
|
14
12
|
master_server() -> global:whereis_name(pm_master).
|
15
13
|
|
16
14
|
loop(Socket) ->
|
17
15
|
case gen_tcp:recv(Socket, 0) of
|
18
|
-
{ok, Data} ->
|
16
|
+
{ok, Data} ->
|
19
17
|
?TRACE("received", [master_server(), erlang:binary_to_list(Data)]),
|
20
18
|
% Args = [Item || K <- string:tokens(erlang:binary_to_list(Data), " "), Item <- erlang:list_to_atom(K)],
|
21
19
|
[Meth|Args] = string:tokens(erlang:binary_to_list(Data), " "),
|
22
20
|
?TRACE("received", [Meth, Args]),
|
23
21
|
Output = gen_server:call(master_server(), {erlang:list_to_atom(Meth), Args}),
|
24
22
|
?TRACE("received from gen_server", [Output]),
|
25
|
-
|
26
|
-
io:format("~p~n", [Output]),
|
23
|
+
send_back_appropriate_response(Socket, Output),
|
27
24
|
?TRACE("posted", [Output]),
|
28
25
|
loop(Socket);
|
29
26
|
{error, closed} ->
|
30
27
|
ok
|
31
28
|
end.
|
32
29
|
|
30
|
+
% send_back_appropriate_response(Socket, Output) when is_float(Output) -> gen_tcp:send(Socket, erlang:float_to_list(Output));
|
31
|
+
% Figure out how to do this the best... damnit
|
32
|
+
send_back_appropriate_response(Socket, Output) ->
|
33
|
+
[H|_T] = Output,
|
34
|
+
case erlang:is_atom(H) of
|
35
|
+
true ->
|
36
|
+
?TRACE("NewOut", [Output]),
|
37
|
+
NewOut = pm_strings:string_join( [erlang:atom_to_list(K) || K <- Output], " ");
|
38
|
+
_ ->
|
39
|
+
ListOfFloats = pm_strings:string_join( [erlang:float_to_list(V) || V <- Output], " "),
|
40
|
+
NewOut = [ListOfFloats]
|
41
|
+
end,
|
42
|
+
?TRACE("NewOut", [NewOut]),
|
43
|
+
gen_tcp:send(Socket, NewOut).
|
44
|
+
|
45
|
+
|
33
46
|
connect_to_master() ->
|
34
47
|
case net_adm:ping(?MASTER_LOCATION) of
|
35
|
-
pong ->
|
48
|
+
pong ->
|
49
|
+
global:sync(),
|
36
50
|
ok;
|
37
51
|
_ ->
|
38
52
|
receive
|
@@ -44,8 +44,7 @@
|
|
44
44
|
get_load(Type) ->
|
45
45
|
% {Loads, _} = pm_cluster:send_call(get_load_for_type, [Type]),
|
46
46
|
% {Loads, _} = gen_server:call(?SERVER, {get_load, [Type]}),
|
47
|
-
|
48
|
-
utils:average_of_list(Loads).
|
47
|
+
gen_server:call(?SERVER, {get_current_load, Type}).
|
49
48
|
|
50
49
|
% Send reconfigure tasks to every node
|
51
50
|
reconfigure_cloud() -> gen_server:cast(?SERVER, {force_reconfig}).
|
@@ -59,7 +58,7 @@ shutdown_cloud() ->
|
|
59
58
|
pm_cluster:send_call(stop, []),
|
60
59
|
{ok}.
|
61
60
|
|
62
|
-
get_current_nodes() -> gen_server:call(?SERVER, {
|
61
|
+
get_current_nodes() -> gen_server:call(?SERVER, {get_current_nodes, []}).
|
63
62
|
|
64
63
|
stop() -> gen_server:cast(?MODULE, stop).
|
65
64
|
%%--------------------------------------------------------------------
|
@@ -101,13 +100,12 @@ handle_call({get_load, Args}, _From, State) ->
|
|
101
100
|
Nodes = pm_cluster:get_live_nodes(),
|
102
101
|
List = rpc:multicall(Nodes, pm_node, get_load_for_type, [Args]),
|
103
102
|
Loads = utils:convert_responses_to_int_list(List),
|
104
|
-
{reply, Loads, State};
|
105
|
-
handle_call({get_current_load,
|
106
|
-
LoadForType = get_load_for_type(Type, State),
|
103
|
+
{reply, Loads, State};
|
104
|
+
handle_call({get_current_load, Types}, _From, State) ->
|
105
|
+
LoadForType = [utils:average_of_list(get_load_for_type(Type, State)) || Type <- Types],
|
107
106
|
?TRACE("LoadForType: ",[LoadForType]),
|
108
|
-
|
109
|
-
|
110
|
-
handle_call({get_live_nodes}, _From, State) ->
|
107
|
+
{reply, LoadForType, State};
|
108
|
+
handle_call({get_current_nodes, _Args}, _From, State) ->
|
111
109
|
{reply, get_live_nodes(State), State}.
|
112
110
|
|
113
111
|
% handle_call(_Request, _From, State) ->
|
@@ -0,0 +1,11 @@
|
|
1
|
+
-module (pm_strings).
|
2
|
+
-include_lib("../include/defines.hrl").
|
3
|
+
-compile(export_all).
|
4
|
+
|
5
|
+
string_join(Items, Sep) ->
|
6
|
+
lists:flatten(lists:reverse(string_join1(Items, Sep, []))).
|
7
|
+
|
8
|
+
string_join1([Head | []], _Sep, Acc) ->
|
9
|
+
[Head | Acc];
|
10
|
+
string_join1([Head | Tail], Sep, Acc) ->
|
11
|
+
string_join1(Tail, Sep, [Sep, Head | Acc]).
|
@@ -1,4 +1,5 @@
|
|
1
1
|
-module (utils).
|
2
|
+
-include_lib("../include/defines.hrl").
|
2
3
|
-compile(export_all).
|
3
4
|
|
4
5
|
-ifdef(EUNIT).
|
@@ -12,18 +13,25 @@ convert_responses_to_int_list(L) ->
|
|
12
13
|
average_for_list(Sum, L).
|
13
14
|
|
14
15
|
% Start a timer to fire off Fun after Time number of milliseconds
|
16
|
+
start_timer(Name, Time, Fun) ->
|
17
|
+
case whereis(Name) of
|
18
|
+
undefined -> register(Name, spawn(fun() -> tick_timer(Time, Fun) end));
|
19
|
+
_ -> ok
|
20
|
+
end.
|
15
21
|
start_timer(Time, Fun) ->
|
16
|
-
|
22
|
+
start_timer(?MODULE, Time, Fun).
|
23
|
+
% register(?MODULE, spawn(fun() -> tick_timer(Time, Fun) end)).
|
17
24
|
|
25
|
+
stop_timer(Name) -> erlang:whereis(Name) ! stop.
|
18
26
|
stop_timer() -> ?MODULE ! stop.
|
19
27
|
|
28
|
+
|
20
29
|
tick_timer(Time, Fun) ->
|
21
30
|
receive
|
22
|
-
stop ->
|
23
|
-
void
|
31
|
+
stop -> void
|
24
32
|
after Time ->
|
25
33
|
Fun(),
|
26
|
-
tick_timer(Time, Fun)
|
34
|
+
tick_timer(Time, Fun)
|
27
35
|
end.
|
28
36
|
|
29
37
|
average_of_list(L) ->
|