auser-poolparty 0.2.53 → 0.2.54

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Manifest.txt +15 -1
  2. data/README.txt +20 -4
  3. data/bin/cloud-ensure-provisioning +13 -7
  4. data/bin/cloud-handle-load +1 -0
  5. data/bin/cloud-start +1 -1
  6. data/bin/messenger-get-current-nodes +13 -0
  7. data/bin/server-show-stats +1 -1
  8. data/bin/server-start-client +15 -8
  9. data/bin/server-start-master +11 -7
  10. data/bin/server-start-node +12 -10
  11. data/bin/server-stop-client +3 -0
  12. data/bin/server-stop-master +3 -0
  13. data/bin/server-stop-node +3 -0
  14. data/lib/erlang/messenger/include/defines.hrl +1 -0
  15. data/lib/erlang/messenger/pm_client_rel-0.1.script +1 -1
  16. data/lib/erlang/messenger/pm_master_rel-0.1.script +1 -1
  17. data/lib/erlang/messenger/pm_node_rel-0.1.script +1 -1
  18. data/lib/erlang/messenger/src/client_server.erl +22 -8
  19. data/lib/erlang/messenger/src/pm_master.erl +7 -9
  20. data/lib/erlang/messenger/src/pm_node.erl +0 -1
  21. data/lib/erlang/messenger/src/pm_strings.erl +11 -0
  22. data/lib/erlang/messenger/src/utils.erl +12 -4
  23. data/lib/erlang/messenger/useful_snippets +1 -1
  24. data/lib/poolparty.rb +10 -2
  25. data/lib/poolparty/base_packages/poolparty.rb +23 -16
  26. data/lib/poolparty/base_packages/runit.rb +21 -0
  27. data/lib/poolparty/helpers/binary.rb +0 -1
  28. data/lib/poolparty/helpers/provisioner_base.rb +4 -0
  29. data/lib/poolparty/helpers/provisioners/master.rb +15 -8
  30. data/lib/poolparty/modules/file_writer.rb +10 -2
  31. data/lib/poolparty/net/messenger.rb +9 -3
  32. data/lib/poolparty/net/remoter.rb +12 -8
  33. data/lib/poolparty/plugins/runit.rb +96 -0
  34. data/lib/poolparty/pool/cloud.rb +1 -1
  35. data/lib/poolparty/pool/resource.rb +2 -1
  36. data/lib/poolparty/pool/resources/custom_service.rb +30 -0
  37. data/lib/poolparty/pool/resources/package.rb +5 -2
  38. data/lib/poolparty/templates/messenger/client/log-run.erb +2 -0
  39. data/lib/poolparty/templates/messenger/client/run.erb +4 -0
  40. data/lib/poolparty/templates/messenger/master/log-run.erb +2 -0
  41. data/lib/poolparty/templates/messenger/master/run.erb +4 -0
  42. data/lib/poolparty/templates/messenger/node/log-run.erb +2 -0
  43. data/lib/poolparty/templates/messenger/node/run.erb +4 -0
  44. data/lib/poolparty/version.rb +1 -1
  45. data/poolparty.gemspec +58 -504
  46. data/spec/poolparty/net/remote_spec.rb +1 -1
  47. metadata +21 -7
@@ -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
- COMING SOON
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
- COMING SOON
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
- poolparty is written with the intention of being as application-agnostic as possible. It installs only the basic
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
- poolparty is easily configuration. In fact, it makes little assumptions about your development environment and allows
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, {:hostname => @hostname}) do
14
+ with_cloud(cloud) do
17
15
 
18
- boot_file = "#{Messenger.append_dir}/pm_node_rel-0.1"
19
- command = Messenger.erl_command(hostname, "-boot #{boot_file} #{noshell ? "" : "-detached -heart"}")
20
-
21
- Kernel.system command
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
@@ -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"
@@ -22,7 +22,7 @@ o.loaded_clouds.each do |cloud|
22
22
  provisioning_complete
23
23
  end
24
24
  end
25
- vputs open(::File.join(File.dirname(__FILE__), "..", "lib", "poolparty", "config", "postlaunchmessage.txt")).read ^ {:master_ip => master.ip.chomp}
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
@@ -8,7 +8,7 @@ end
8
8
 
9
9
  o.loaded_clouds.each do |cloud|
10
10
 
11
- with_cloud(cloud, {:testing => o.testing }) do
11
+ with_cloud(cloud) do
12
12
  puts header("Stats")
13
13
  puts rules_values
14
14
  end
@@ -10,13 +10,20 @@ end
10
10
  o.loaded_clouds.each do |cloud|
11
11
 
12
12
  with_cloud(cloud) do
13
-
14
- boot_file = "#{Messenger.append_dir}/pm_client_rel-0.1"
15
- command = Messenger.erl_command("client", "-boot #{boot_file} #{noshell ? "" : "-detached -heart -noshell"}", 7050, 7050)
16
-
17
- vputs "Running #{command}"
18
-
19
- Kernel.system "#{command}" unless testing
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
+
@@ -10,13 +10,17 @@ end
10
10
  o.loaded_clouds.each do |cloud|
11
11
 
12
12
  with_cloud(cloud) do
13
-
14
- boot_file = "#{Messenger.append_dir}/pm_master_rel-0.1"
15
- command = Messenger.erl_command("master", "-boot #{boot_file} #{noshell ? "" : "-noshell -detached -heart"}")
16
-
17
- vputs "Running #{command}"
18
-
19
- Kernel.system "#{command}" unless testing
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
@@ -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
- # SECURITY RISK
21
-
22
- boot_file = "#{Messenger.append_dir}/pm_node_rel-0.1"
23
- command = Messenger.erl_command(hostname, "-boot #{boot_file} #{noshell ? "" : "-noshell -detached -heart"} -- #{available_monitors.join(" ")}")
24
-
25
- vputs "Running #{command}"
26
-
27
- Kernel.system "#{command}" unless testing
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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ ps aux | grep -v grep | grep client_service | awk '{print $2}' | xargs kill
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ ps aux | grep -v grep | grep pm_master | awk '{print $2}' | xargs kill
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ ps aux | grep -v grep | grep pm_node | awk '{print $2}' | xargs kill
@@ -17,6 +17,7 @@
17
17
  -define (MASTER_NODE_NAME, master).
18
18
  -define (MASTER_SERVER, global:whereis_name(pm_master)).
19
19
 
20
+ -define (UPDATE_TIME, 10000).
20
21
  -define(DICT, dict).
21
22
  -record (node,
22
23
  {load}).
@@ -1,4 +1,4 @@
1
- %% script generated at {2008,11,10} {16,32,20}
1
+ %% script generated at {2008,11,11} {20,31,50}
2
2
  {script,
3
3
  {"client","0.1"},
4
4
  [{preLoaded,
@@ -1,4 +1,4 @@
1
- %% script generated at {2008,11,10} {16,32,20}
1
+ %% script generated at {2008,11,11} {20,31,50}
2
2
  {script,
3
3
  {"master","0.1"},
4
4
  [{preLoaded,
@@ -1,4 +1,4 @@
1
- %% script generated at {2008,11,10} {16,32,20}
1
+ %% script generated at {2008,11,11} {20,31,50}
2
2
  {script,
3
3
  {"node","0.1"},
4
4
  [{preLoaded,
@@ -5,34 +5,48 @@
5
5
 
6
6
  -define (RECONNECT_TIMEOUT, 10000).
7
7
 
8
- start() ->
9
- ?TRACE("MASTER_LOCATION", [?MASTER_LOCATION]),
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
- gen_tcp:send(Socket, erlang:float_to_list(Output)),
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
- Loads = gen_server:call(?SERVER, {get_current_load, Type}),
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, {get_live_nodes}).
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, [Type]}, _From, State) ->
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
- Loads = utils:average_of_list(LoadForType),
109
- {reply, Loads, State};
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) ->
@@ -21,7 +21,6 @@
21
21
  monitors = {} % Tuple of monitors
22
22
  }).
23
23
  -define(SERVER, ?MODULE).
24
- -define (UPDATE_TIME, 10000).
25
24
 
26
25
  % Client function definitions
27
26
  -export ([stop/0]).
@@ -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
- register(?MODULE, spawn(fun() -> tick_timer(Time, Fun) end)).
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) ->