phantom-manager 0.0.2

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.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +3 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.lock +36 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +32 -0
  8. data/Rakefile +1 -0
  9. data/bin/phantom_monitor +55 -0
  10. data/config/config.yml +27 -0
  11. data/lib/.DS_Store +0 -0
  12. data/lib/monitors/base.rb +39 -0
  13. data/lib/monitors/memory.rb +27 -0
  14. data/lib/monitors/processes.rb +33 -0
  15. data/lib/monitors/restart_listener.rb +39 -0
  16. data/lib/monitors/violations_recorders/base.rb +56 -0
  17. data/lib/monitors/violations_recorders/memory.rb +23 -0
  18. data/lib/monitors/violations_recorders/processes.rb +21 -0
  19. data/lib/nginx/manager.rb +59 -0
  20. data/lib/phantom/.DS_Store +0 -0
  21. data/lib/phantom/collector.rb +36 -0
  22. data/lib/phantom/manager/version.rb +5 -0
  23. data/lib/phantom/manager.rb +34 -0
  24. data/lib/phantom/process.rb +54 -0
  25. data/lib/utils/cfg.rb +18 -0
  26. data/lib/utils/limited_array.rb +31 -0
  27. data/lib/utils/lock.rb +29 -0
  28. data/lib/utils/logger.rb +3 -0
  29. data/lib/utils/shell.rb +12 -0
  30. data/phantom-manager.gemspec +24 -0
  31. data/spec/files/config.yml +12 -0
  32. data/spec/files/nginx.conf +26 -0
  33. data/spec/lib/monitors/base_spec.rb +14 -0
  34. data/spec/lib/monitors/memory_spec.rb +33 -0
  35. data/spec/lib/monitors/processes_spec.rb +45 -0
  36. data/spec/lib/monitors/restart_listener_spec.rb +27 -0
  37. data/spec/lib/monitors/violations_recorders/base_spec.rb +126 -0
  38. data/spec/lib/monitors/violations_recorders/memory_spec.rb +73 -0
  39. data/spec/lib/monitors/violations_recorders/processes_spec.rb +51 -0
  40. data/spec/lib/nginx/manager_spec.rb +80 -0
  41. data/spec/lib/phantom/collector_spec.rb +59 -0
  42. data/spec/lib/phantom/manager_spec.rb +25 -0
  43. data/spec/lib/phantom/process_spec.rb +47 -0
  44. data/spec/lib/utils/limited_array_spec.rb +73 -0
  45. data/spec/lib/utils/lock_spec.rb +69 -0
  46. data/spec/lib/utils/shell_spec.rb +18 -0
  47. data/spec/shared_spec.rb +45 -0
  48. data/spec/spec_helper.rb +29 -0
  49. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmU1OGFhZGQzODQ2NGJkNjkwN2FkNmM3ZWI0NTE3ZWI4NWY5NzEzNA==
5
+ data.tar.gz: !binary |-
6
+ OGJjMTI5Yzk1NjVkOTczM2E4MWEyMWM2NGMxMmQ2NmUzMTdmYjM4MQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MjI3NTQwNDNjMWVmZGRkYzk3NTYzZmE5MDE4YmFiNjkwNGFjMmJmMTkxOWE0
10
+ ODQ1MTRlYzY3NjFmMzg0MjZiZGM5ODIyOWY2NWY0NjgyNWQ0M2UyZTI1NDcx
11
+ MDMxZmUzZTM0OTVlMzFmYWVkNGI5MGNiZDVjNzEyYTU2Yjk3MmE=
12
+ data.tar.gz: !binary |-
13
+ MzdkODY4MjYyNDc4MzFmYjlhNTc2NGE2MDg5MmMwOWI2ZTA4ZjI4ZTkwYTA2
14
+ YTU2OTdlMjI4MmJlNmUyZmEyYzAxOWE5ZWQyMzQzZDdiNWRmYWFmNWM5ODRj
15
+ ZDJiYjUzNTU4YzcyYmYyZTlhZTdiZmEzZDhmOGUzYTQwZjcyMGQ=
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .rspec
2
+ tags
3
+ log/*
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in phantom-manager.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
7
+ gem 'debugger'
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ phantom-manager (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ columnize (0.3.6)
10
+ debugger (1.4.0)
11
+ columnize (>= 0.3.1)
12
+ debugger-linecache (~> 1.1.1)
13
+ debugger-ruby_core_source (~> 1.2.0)
14
+ debugger-linecache (1.1.2)
15
+ debugger-ruby_core_source (>= 1.1.1)
16
+ debugger-ruby_core_source (1.2.0)
17
+ diff-lcs (1.2.4)
18
+ rake (10.1.0)
19
+ rspec (2.14.1)
20
+ rspec-core (~> 2.14.0)
21
+ rspec-expectations (~> 2.14.0)
22
+ rspec-mocks (~> 2.14.0)
23
+ rspec-core (2.14.3)
24
+ rspec-expectations (2.14.0)
25
+ diff-lcs (>= 1.1.3, < 2.0)
26
+ rspec-mocks (2.14.1)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.3)
33
+ debugger
34
+ phantom-manager!
35
+ rake
36
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Erez Rabih
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Phantom::Manager
2
+
3
+ phantom-manager allows you to use multiple phantom-js processes behind an Nginx
4
+ server. It will manage both presence and memory consumption of those processes
5
+ and kill them when appropriate, all this in sync with the Nginx configuration
6
+ so that all requests will get answered.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'phantom-manager'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install phantom-manager
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ $options = {}
6
+
7
+ optparse = OptionParser.new do |opts|
8
+
9
+ opts.on( '-h', '--help', 'Display this screen' ) do
10
+ puts opts
11
+ exit
12
+ end
13
+
14
+ opts.on( '-c', '--config FILE', "Configuration file path" ) do |f|
15
+ $options[:config] = f
16
+ end
17
+
18
+ opts.on( '-e', '--env ENVIRONMENT', "Environment to run on" ) do |env|
19
+ $options[:env] = env
20
+ end
21
+
22
+ end
23
+
24
+ optparse.parse!
25
+
26
+ mandatory = [:env, :config]
27
+ missing = mandatory.select{ |param| $options[param].nil? }
28
+
29
+ if not missing.empty?
30
+ puts "Missing options: #{missing.join(', ')}"
31
+ puts optparse
32
+ exit
33
+ end
34
+
35
+ lib = File.expand_path('../../lib', __FILE__)
36
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
37
+
38
+ require 'phantom/manager/version'
39
+ require 'monitors/memory.rb'
40
+ require 'monitors/processes.rb'
41
+ require 'monitors/restart_listener.rb'
42
+
43
+
44
+ $logger.info "Starting for environment #{$options[:env]} with config file #{$options[:config]}... "
45
+
46
+ Monitors::RestartListener.run
47
+
48
+ $logger.info "Running memory monitor for phantom processes"
49
+
50
+ Thread.new {Monitors::Memory.run}
51
+
52
+ $logger.info "Running processes monitor for phantom processes"
53
+
54
+ Thread.new {Monitors::Processes.run}.join
55
+
data/config/config.yml ADDED
@@ -0,0 +1,27 @@
1
+ development:
2
+ #The time to wait between removing a phantomjs from the nginx configuration
3
+ #and actually killing the process.
4
+ phantom_termination_grace: 10
5
+
6
+ #This will be the inital port of the phantomjs processes. For example if
7
+ #phantom_processes_number is set to 4 and phantom_base_port to 8000 then 4
8
+ #phantoms, on ports 8000 8001 8002 8003 will be managed.
9
+ phantom_base_port: 8002
10
+ #Rails root path
11
+ rails_root: "path"
12
+ #Path to your nginx configuration
13
+ nginx_conf: "path"
14
+ #Path to where you want to keep your phantomjs logs
15
+ phantom_log_path: "path"
16
+ #If a phantom process memory consumption is over memory_limit for
17
+ #memory_retries times with memory_check_interval between each check it will
18
+ #be killed
19
+ memory_limit: 150000
20
+ memory_retries: 5
21
+ memory_check_interval: 5
22
+ #Number of phantom processes to keep up. If a process is missing for
23
+ #processes_check_retries times in a row with processes_check_interval between
24
+ #checks it will be brought up byt the monitor
25
+ phantom_processes_number: 10
26
+ processes_check_retries: 3
27
+ processes_check_interval: 15
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,39 @@
1
+ require 'utils/cfg'
2
+ require 'utils/logger'
3
+ require 'phantom/manager'
4
+ require 'phantom/collector'
5
+
6
+ module Monitors
7
+ class Base
8
+
9
+ class << self
10
+ def run(custom_logger = $logger)
11
+ @logger = custom_logger
12
+
13
+ loop do
14
+ perform_check
15
+ sleep check_interval
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def perform_check
22
+ raise NotImplementedError.new
23
+ end
24
+
25
+ def check_interval
26
+ raise NotImplementedError.new
27
+ end
28
+
29
+ def running_instances
30
+ Phantom::Collector.get_running_instances
31
+ end
32
+
33
+ def logger
34
+ @logger ||= $logger
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ require 'monitors/base'
2
+ require 'monitors/violations_recorders/memory'
3
+
4
+ module Monitors
5
+ class Memory < Base
6
+
7
+ class << self
8
+
9
+ def perform_check
10
+ logger.info "Perfoming memory check..."
11
+
12
+ running_instances.each do |p|
13
+ if ViolationsRecorders::Memory.is_violating?(p)
14
+ logger.info "process #{p.pid} was found bad!"
15
+ Phantom::Manager.restart(p)
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ def check_interval
22
+ $cfg.memory_check_interval
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ require 'monitors/base'
2
+ require 'monitors/violations_recorders/processes'
3
+
4
+ module Monitors
5
+ class Processes < Base
6
+
7
+ class << self
8
+ def perform_check
9
+ logger.info "Perfoming processes check..."
10
+
11
+ missing_processes = Phantom::Collector.missing_ports.map do |port|
12
+ p = Phantom::Process.new
13
+ p.port = port
14
+ p
15
+ end
16
+
17
+ missing_processes.each do |p|
18
+ if ViolationsRecorders::Processes.is_violating?(p)
19
+ logger.info "found missing phantom on port #{p.port}"
20
+ Phantom::Manager.start(p)
21
+ end
22
+ end
23
+
24
+ logger.info "All processes are running OK" if missing_processes.empty?
25
+ end
26
+
27
+ def check_interval
28
+ $cfg.processes_check_interval
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ require 'phantom/manager'
2
+ require 'utils/logger'
3
+ require 'utils/cfg'
4
+ require 'utils/lock'
5
+
6
+
7
+ module Monitors
8
+ class RestartListener
9
+ class << self
10
+
11
+ def run
12
+ Signal.trap("USR2") do
13
+ respond_to_signal
14
+ end
15
+ end
16
+
17
+ def respond_to_signal
18
+ lock.acquire do
19
+ $logger.info "Initiating restart sequence"
20
+
21
+ Phantom::Collector.get_running_instances.each do |p|
22
+ $logger.info "Restarting process on port #{p.port}"
23
+ Phantom::Manager.restart(p)
24
+
25
+ sleep $cfg.phantom_termination_grace
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def lock
34
+ @lock ||= Utils::Lock.new
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ require 'utils/logger'
2
+ require 'utils/cfg'
3
+ require 'phantom/process'
4
+
5
+ module Monitors
6
+ module ViolationsRecorders
7
+ class Base
8
+
9
+ class << self
10
+
11
+ def inherited(base)
12
+ base.instance_variable_set(:@violations, {})
13
+ end
14
+
15
+ @violations = {}
16
+
17
+ def reset
18
+ @violations = {}
19
+ end
20
+
21
+ def is_violating?(process)
22
+ $logger.debug "checking #{process}"
23
+ update_violations_count(process)
24
+ $logger.debug "#{@violations}"
25
+ violating = @violations[process.send(process_attr)] == retries_limit
26
+ @violations[process.send(process_attr)] = 0 if violating
27
+ violating
28
+ end
29
+
30
+ protected
31
+
32
+ def update_violations_count(process)
33
+ @violations[process.send(process_attr)] ||= 0
34
+ if process_is_violating?(process)
35
+ @violations[process.send(process_attr)]+= 1
36
+ else
37
+ @violations[process.send(process_attr)] = 0
38
+ end
39
+ end
40
+
41
+ def retries_limit
42
+ raise NotImplementedError
43
+ end
44
+
45
+ def process_attr
46
+ raise NotImplementedError
47
+ end
48
+
49
+ def process_is_violating?(process)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require 'monitors/violations_recorders/base'
2
+
3
+ module Monitors
4
+ module ViolationsRecorders
5
+ class Memory < Base
6
+ class << self
7
+
8
+ def retries_limit
9
+ $cfg.memory_retries
10
+ end
11
+
12
+ def process_attr
13
+ :pid
14
+ end
15
+
16
+ def process_is_violating?(process)
17
+ process.memory_usage > $cfg.memory_limit
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'monitors/violations_recorders/base'
2
+
3
+ module Monitors
4
+ module ViolationsRecorders
5
+ class Processes < Base
6
+ class << self
7
+ def retries_limit
8
+ $cfg.processes_check_retries
9
+ end
10
+
11
+ def process_attr
12
+ :port
13
+ end
14
+
15
+ def process_is_violating?(process)
16
+ Phantom::Collector.missing_ports.include?(process.port)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ require 'utils/logger'
2
+ require 'utils/cfg'
3
+
4
+ module Nginx
5
+ class Manager
6
+ class << self
7
+
8
+ def remove(port)
9
+ $logger.info "removing #{port} from nginx"
10
+ modify_nginx do |ofile, iline|
11
+ ofile.puts(iline) unless iline =~ /#{port}/
12
+ end
13
+ end
14
+
15
+ def add(port)
16
+ $logger.info "adding #{port} to nginx"
17
+ if !port_defined?(port)
18
+ modify_nginx do |ofile, iline|
19
+ ofile.puts(iline)
20
+ ofile.puts(phantom_upstream(port)) if iline =~ /upstream phantomjs/
21
+ end
22
+ else
23
+ $logger.info "port #{port} already defined"
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def phantom_upstream(port)
30
+ " server 127.0.0.1:#{port} fail_timeout=0; # #{Time.now}"
31
+ end
32
+
33
+ def port_defined?(port)
34
+ File.readlines($cfg.nginx_conf).grep(/#{port}/).size > 0
35
+ end
36
+
37
+ def switch_nginx_configs
38
+ $logger.info "switching nginx configurations"
39
+ `mv #{$cfg.new_nginx_conf} #{$cfg.nginx_conf}`
40
+ end
41
+
42
+ def reload_nginx
43
+ $logger.info "reloading nginx"
44
+ `nginx -s reload`
45
+ end
46
+
47
+ def modify_nginx
48
+ File.open($cfg.new_nginx_conf, "w") do |ofile|
49
+ File.foreach($cfg.nginx_conf) do |iline|
50
+ yield ofile, iline
51
+ end
52
+ end
53
+ switch_nginx_configs
54
+ reload_nginx
55
+ end
56
+
57
+ end
58
+ end
59
+ end
Binary file
@@ -0,0 +1,36 @@
1
+ require 'phantom/process'
2
+
3
+ module Phantom
4
+ class Collector
5
+ class << self
6
+
7
+ def on_port(port)
8
+ get_running_instances.find {|p| p.port == port}
9
+ end
10
+
11
+ def get_running_instances
12
+ lines = running_phantoms_shell_output.split("\n")
13
+ lines.map {|l| Phantom::Process.from_string(l) }
14
+ end
15
+
16
+ def missing_ports
17
+ required_ports - get_running_instances.map(&:port)
18
+ end
19
+
20
+ def required_ports
21
+ ($cfg.phantom_base_port..($cfg.phantom_base_port+$cfg.phantom_processes_number-1)).to_a
22
+ end
23
+
24
+ private
25
+
26
+ def running_phantoms_shell_output
27
+ `#{running_phantoms_shell_command}`
28
+ end
29
+
30
+ def running_phantoms_shell_command
31
+ "ps -e -www -o pid,rss,command | grep 'phantomjs' | grep -v sh | grep -v grep"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ module Phantom
2
+ module Manager
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ require "phantom/manager/version"
2
+ require 'utils/logger'
3
+ require 'utils/cfg'
4
+ require 'nginx/manager'
5
+
6
+ module Phantom
7
+ module Manager
8
+ class << self
9
+
10
+ def restart(process)
11
+ $logger.info "restarting process #{process}"
12
+ stop process
13
+ start process
14
+ end
15
+
16
+ def start(process)
17
+ $logger.info "starting process #{process.port}"
18
+ process.start
19
+ Nginx::Manager.add(process.port)
20
+ end
21
+
22
+ private
23
+
24
+ def stop(process)
25
+ $logger.info "stopping process #{process}"
26
+ Nginx::Manager.remove(process.port)
27
+ sleep $cfg.phantom_termination_grace
28
+ process.kill
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,54 @@
1
+ require 'utils/logger'
2
+ require 'utils/cfg'
3
+ require 'utils/shell'
4
+
5
+ module Phantom
6
+ class Process
7
+ attr_accessor :pid, :memory_usage, :command, :port
8
+
9
+ class << self
10
+
11
+ def from_string(str)
12
+ args = parse_string(str)
13
+ new(*args)
14
+ end
15
+
16
+ private
17
+
18
+ def parse_string(str)
19
+ parts = str.split
20
+ pid = parts[0].to_i
21
+ memory_usage = parts[1].to_i
22
+ command = parts[2..-1].join(" ")
23
+ port = parts.last.to_i
24
+
25
+ [pid, memory_usage, command, port]
26
+ end
27
+
28
+ end
29
+
30
+ def initialize(pid = nil, memory_usage = nil, command = nil, port = nil)
31
+ @pid = pid
32
+ @memory_usage = memory_usage
33
+ @command = command
34
+ @port = port
35
+ end
36
+
37
+ def kill
38
+ $logger.info "killing #{self}"
39
+ Utils::Shell.execute "kill #{pid}"
40
+ end
41
+
42
+ def start
43
+ $logger.info "starting phantomjs on port #{port}"
44
+ Utils::Shell.execute start_command
45
+ end
46
+
47
+ private
48
+
49
+ def start_command
50
+ "cd #{$cfg.rails_root} && phantomjs rndrme.js #{port} >>#{$cfg.phantom_log_path} 2>&1 &"
51
+ end
52
+
53
+ end
54
+ end
data/lib/utils/cfg.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'ostruct'
2
+ require 'yaml'
3
+
4
+ class Cfg
5
+ def self.load
6
+ cfg = File.expand_path($options[:config])
7
+ env = $options[:env]
8
+ hash = YAML.load(File.open(cfg))[env]
9
+ hash.each do |k,v|
10
+ hash[k] = v.to_i if v =~ /^\d+$/
11
+ end
12
+ obj = OpenStruct.new hash
13
+ obj.new_nginx_conf = "#{obj.nginx_conf}.new"
14
+
15
+ $cfg = obj
16
+ end
17
+ end
18
+ Cfg.load