phantom-manager 0.0.7 → 0.0.8

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjQ1YmY5NmYzMDgwMjAxN2MzMGU3OTcyMDZkM2NiNmQzNjNlMzRiZA==
4
+ MjUzNTIwOGY1MmNjYjRlNTAxYmZjZjJhNDllMjYxNjVkN2JiOGZmZg==
5
5
  data.tar.gz: !binary |-
6
- NzNiNWE1MTgxYTI1N2RlMDJhZjc1YmE2YWM5ZWQ2M2RmNGRjYjg2NA==
6
+ MzRjOWYwZTBmN2JmMWY4NTc0M2UxY2UwMGQwYmJmOWQyZmRjNmQwOA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NzA2YzFlMTM1NGUxZGVlMDdiNmEwYjMxYjVkNWE0MzdiZTAyMzMyMmI2NGI2
10
- ODNlMzljM2IxN2RhMzBmMGY1NzNjMmViMTVlZDZlMWMzZmU4ZDUyOTA0OTA0
11
- N2MwMjFkNDE0ZjhlYjdmYWMzN2M3MDkxYjVjMTdiMjQyMDllMzM=
9
+ MjEzMTA0NjNkOTIxMGMxY2YzM2E1NDE0OWI4YzZkYzRlYzY2ZGYxYTYzMmIz
10
+ MWVlNjIxOTA4MzZhNWQ3ZGUwMGM5MDMxYzg3NDQ5MThiMDk5YWViMWEwY2M4
11
+ NWFlMmQ2YTFjNWQ2MDlhNTQzYzUxYTBiMTJlZDM3ODQ2YzcwNmI=
12
12
  data.tar.gz: !binary |-
13
- ODg4OGQ5YzNlZmJhNjMxZTA0NmQxNGNkM2E2MTUzYjhjZWI4NDVlMmFkYmYz
14
- MjliOWM3YWI2MjA3MTI1ZTg3OTlkMDdiNzVjYWIyYWFlOTVmODQ4NzJjNjFl
15
- Y2E1MGJhNmQ1NDAzYjAyNWQ0NWVmNDYyZDVjYTJhYWMxZDdjZmI=
13
+ MzEzNDM5NGE4YjZiNzJjYmQxY2IyN2FmNTAxYTkwZWNjNTc4MzdhYWY0ZWM0
14
+ MWQ0ZjhkMjZiYmJiNTJhYTUyZjJjOGYxY2FlNWY1NjU0OWVkNzhiNzQ0MTA5
15
+ ZWNkNjA1ODZkMjBmZjUzMmI3M2U2NDhkZmI5NGVkNjMzY2NiNDk=
data/Gemfile CHANGED
@@ -5,3 +5,4 @@ gemspec
5
5
 
6
6
  gem 'rspec'
7
7
  gem 'debugger'
8
+
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- phantom-manager (0.0.5)
4
+ phantom-manager (0.0.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,10 +1,25 @@
1
1
  # Phantom::Manager
2
2
 
3
+ The architecture behind phantom-manager is:
4
+
5
+ ![Phantom-Architecture](http://i39.tinypic.com/2gxnz3d.png)
6
+
3
7
  phantom-manager allows you to use multiple phantom-js processes behind an Nginx
4
8
  server. It will manage both presence and memory consumption of those processes
5
9
  and kill them when appropriate, all this in sync with the Nginx configuration
6
10
  so that all requests will get answered.
7
11
 
12
+ If you've got a singlepage application and you want to:
13
+ * Render full page for GoogleBot or other web crawlers.
14
+ * Render full page to be cached by your CDN.
15
+
16
+ While:
17
+
18
+ * Keeping your phantom-js processes running.
19
+ * Preventing your phantom-js processes from memory-bloat.
20
+
21
+ This is a good way to achieve it.
22
+
8
23
  ## Installation
9
24
 
10
25
  Add this line to your application's Gemfile:
@@ -21,7 +36,88 @@ Or install it yourself as:
21
36
 
22
37
  ## Usage
23
38
 
24
- TODO: Write usage instructions here
39
+ 1. You will need Nginx which will load balance requests between the phantom-js
40
+ processes.
41
+ Its conf must include a "upstream phantomjs" directive with the corresponding
42
+ settings. For example:
43
+
44
+ ```
45
+ upstream phantomjs {
46
+ server 127.0.0.1:8002;
47
+ server 127.0.0.1:8003;
48
+ server 127.0.0.1:8004;
49
+ server 127.0.0.1:8005;
50
+ }
51
+ ```
52
+
53
+ 2. A customized [rndr.me js](https://github.com/jed/rndr.me) file that will fit your configuration. There is an
54
+ example rndrme.js [here](lib/utils/rndrme.js).
55
+
56
+ The host configuration is where phantom-js requests the page from, so be
57
+ sure to point it to your backend server.
58
+ Also, set the readyEvent to be the event you'r raising so that phantom-js
59
+ identify it should start rendering.
60
+
61
+ 3. Create a config.yml file to set the variables for phantom-manager. There's
62
+ an [example config](config/config.yml) with a documentation of each attribute and its meaning.
63
+
64
+ 4. You're ready to run phantom-manager:
65
+ Just run `phantom_monitor` from anywhere in your system to get the usage
66
+ instructions for the command line tool.
67
+
68
+ Usually, you would just `phantom_monitor -c YOUR_CONF_FILE -e YOUR_ENV`
69
+ The env option is there to allow your config.yml to have multiple
70
+ environments settings.
71
+
72
+ 5. The phantom_monitor process listens for USR2 signals. Once such a signal is
73
+ sent it will restart all processes one by one.
74
+
75
+ ## How Does It Work?
76
+
77
+ Phantom manager will check for both presence and memory consumption of your
78
+ phantom-js processes under the configuration you have defined.
79
+
80
+ ### Presence
81
+
82
+ Assuming configuration:
83
+
84
+ ```
85
+ phantom_base_port: 8002
86
+ phantom_processes_number: 3
87
+ ```
88
+ The monitor will keep phantom-js processes up on ports 8002, 8003, 8004
89
+ If a phantom-js crashes the monitor will bring it back up.
90
+
91
+ ### Memory Consumption
92
+
93
+ Assuming configuration:
94
+ ```
95
+ memory_limit: 100_000
96
+ memory_retries: 3
97
+ memory_check_interval: 5
98
+ ```
99
+ The monitor sample all phantom-js processes each 5 seconds and restart those
100
+ which their memory exceeded 100MB for 3 straight samples.
101
+
102
+ ### Restarting Processes
103
+
104
+ To restart a phantom-js process the monitor performs the following actions:
105
+
106
+ 1. Remove the process from nginx upstream config.
107
+
108
+ 2. Reload Nginx.
109
+
110
+ 3. Sleeps for phantom_termination_grace seconds to allow this phantom to
111
+ respond to all requests in its request queue.
112
+
113
+ 4. Kill the phantom-js process.
114
+
115
+ 5. Start the phantom-js process on the same port.
116
+
117
+ 6. Add it to the Nginx upstream configuration.
118
+
119
+ 7. Reload Nginx.
120
+
25
121
 
26
122
  ## Contributing
27
123
 
data/bin/phantom_monitor CHANGED
@@ -42,9 +42,10 @@ lib = File.expand_path('../../lib', __FILE__)
42
42
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
43
43
 
44
44
  require 'phantom/manager/version'
45
- require 'monitors/memory.rb'
46
- require 'monitors/processes.rb'
47
- require 'monitors/restart_listener.rb'
45
+ require 'monitors/memory'
46
+ require 'monitors/processes'
47
+ require 'monitors/response_time'
48
+ require 'monitors/restart_listener'
48
49
 
49
50
 
50
51
  $logger.info "Starting for environment #{$options[:env]} with config file #{$options[:config]}... "
@@ -57,5 +58,8 @@ Thread.new {Monitors::Memory.run}
57
58
 
58
59
  $logger.info "Running processes monitor for phantom processes"
59
60
 
60
- Thread.new {Monitors::Processes.run}.join
61
+ Thread.new {Monitors::Processes.run}
61
62
 
63
+ $logger.info "Running processes monitor for response time"
64
+
65
+ Thread.new {Monitors::ResponseTime.run}.join
data/config/config.yml CHANGED
@@ -28,3 +28,11 @@ development:
28
28
 
29
29
  #Command to issue when launching phantomjs
30
30
  phantom_command: 'phantomjs rndrme.js'
31
+
32
+
33
+ #Response Time Monitor
34
+ #
35
+ # If a phantomjs process excceeds this time in response it will be killed
36
+ response_time_threshold: 2
37
+ response_time_check_retries: 2
38
+ response_time_check_interval: 40
data/lib/monitors/base.rb CHANGED
@@ -5,12 +5,14 @@ require 'phantom/collector'
5
5
 
6
6
  module Monitors
7
7
  class Base
8
+ extend Logging
8
9
 
9
10
  class << self
10
11
  def run(custom_logger = $logger)
11
12
  @logger = custom_logger
12
13
 
13
14
  loop do
15
+ log "Performing Check..."
14
16
  perform_check
15
17
  sleep check_interval
16
18
  end
@@ -30,9 +32,6 @@ module Monitors
30
32
  Phantom::Collector.get_running_instances
31
33
  end
32
34
 
33
- def logger
34
- @logger ||= $logger
35
- end
36
35
  end
37
36
 
38
37
  end
@@ -7,11 +7,9 @@ module Monitors
7
7
  class << self
8
8
 
9
9
  def perform_check
10
- logger.info "Perfoming memory check..."
11
-
12
10
  running_instances.each do |p|
13
11
  if ViolationsRecorders::Memory.is_violating?(p)
14
- logger.info "process #{p.pid} was found bad!"
12
+ log "process #{p.pid} had a memory violation"
15
13
  Phantom::Manager.restart(p)
16
14
  end
17
15
  end
@@ -6,8 +6,6 @@ module Monitors
6
6
 
7
7
  class << self
8
8
  def perform_check
9
- logger.info "Perfoming processes check..."
10
-
11
9
  missing_processes = Phantom::Collector.missing_ports.map do |port|
12
10
  p = Phantom::Process.new
13
11
  p.port = port
@@ -16,12 +14,12 @@ module Monitors
16
14
 
17
15
  missing_processes.each do |p|
18
16
  if ViolationsRecorders::Processes.is_violating?(p)
19
- logger.info "found missing phantom on port #{p.port}"
17
+ log "found missing phantom on port #{p.port}"
20
18
  Phantom::Manager.start(p)
21
19
  end
22
20
  end
23
21
 
24
- logger.info "All processes are running OK" if missing_processes.empty?
22
+ log "All processes are running OK" if missing_processes.empty?
25
23
  end
26
24
 
27
25
  def check_interval
@@ -0,0 +1,27 @@
1
+ require 'monitors/base'
2
+ require 'monitors/violations_recorders/response_time'
3
+
4
+ module Monitors
5
+ class ResponseTime < Base
6
+
7
+ class << self
8
+ def perform_check
9
+ all_processes_ok = true
10
+ running_instances.each do |p|
11
+ if ViolationsRecorders::ResponseTime.is_violating?(p)
12
+ all_processes_ok = false
13
+ log "process #{p} had a response time violation"
14
+ Phantom::Manager.restart(p)
15
+ end
16
+ end
17
+
18
+ log "All response time are ok" if all_processes_ok
19
+ end
20
+
21
+ def check_interval
22
+ Cfg.response_time_check_interval
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -5,6 +5,7 @@ require 'phantom/process'
5
5
  module Monitors
6
6
  module ViolationsRecorders
7
7
  class Base
8
+ extend Logging
8
9
 
9
10
  class << self
10
11
 
@@ -19,9 +20,9 @@ module Monitors
19
20
  end
20
21
 
21
22
  def is_violating?(process)
22
- $logger.debug "checking #{process}"
23
+ log "checking #{process}", :debug
23
24
  update_violations_count(process)
24
- $logger.debug "#{@violations}"
25
+ log "#{@violations}", :debug
25
26
  violating = @violations[process.send(process_attr)] == retries_limit
26
27
  @violations[process.send(process_attr)] = 0 if violating
27
28
  violating
@@ -0,0 +1,41 @@
1
+ require 'monitors/violations_recorders/base'
2
+ require 'timeout'
3
+
4
+ module Monitors
5
+ module ViolationsRecorders
6
+ class ResponseTime < Base
7
+ class << self
8
+ def retries_limit
9
+ Cfg.response_time_check_retries
10
+ end
11
+
12
+ def process_attr
13
+ :pid
14
+ end
15
+
16
+ def process_is_violating?(process)
17
+ time = Cfg.response_time_threshold
18
+ begin
19
+ Timeout.timeout(Cfg.response_time_threshold) do
20
+ time = check_response_time(process)
21
+ end
22
+ rescue Timeout::Error
23
+ return true
24
+ end
25
+ time > Cfg.response_time_threshold
26
+ end
27
+
28
+ private
29
+
30
+ def check_response_time(process)
31
+ res = `curl -o /dev/null -s -w %{time_total}@%{http_code} #{process_url(process)}`
32
+ res.split("@").first.to_f
33
+ end
34
+
35
+ def process_url(process)
36
+ "http://localhost:#{process.port}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  module Phantom
2
2
  module Manager
3
- VERSION = "0.0.7"
3
+ VERSION = "0.0.8"
4
4
  end
5
5
  end
data/lib/utils/logger.rb CHANGED
@@ -1,3 +1,13 @@
1
1
  require 'logger'
2
2
  $logger = Logger.new(STDOUT)
3
3
  $logger.level = Logger::INFO
4
+
5
+ module Logging
6
+ def logger
7
+ @logger ||= $logger
8
+ end
9
+
10
+ def log(str, severity = :info)
11
+ logger.send(severity,"[#{self.name}] #{str}")
12
+ end
13
+ end
@@ -0,0 +1,111 @@
1
+ var config = {
2
+ host: "http://localhost:8001",
3
+ maxTime: 30000,
4
+ maxBytes: 0x100000,
5
+ readyEvent: "renderReady",
6
+ loadImages: false
7
+ }
8
+
9
+
10
+ var system = require("system")
11
+ var webserver = require("webserver")
12
+ var webpage = require("webpage")
13
+
14
+ var port = system.args[1];
15
+
16
+ if (!port) {
17
+ console.error("No port specified in " + configPath)
18
+ phantom.exit(1)
19
+ }
20
+
21
+ var server = webserver.create()
22
+ var listening = server.listen(port, onRequest)
23
+
24
+ if (!listening) {
25
+ console.error("Could not bind to port " + port)
26
+ phantom.exit(1)
27
+ }
28
+
29
+ function onRequest(req, res) {
30
+ var page = webpage.create()
31
+ var bytesConsumed = 0
32
+
33
+ if (req.method != "GET") {
34
+ return send(405, toHTML("Method not accepted."))
35
+ }
36
+
37
+ var url = parse(req.url)
38
+
39
+ var query = url.query
40
+ var href = decodeURIComponent(config.host + req.url)
41
+
42
+ if (!href) {
43
+ return send(400, toHTML("`href` parameter is missing."))
44
+ }
45
+
46
+ var maxTime = Number(query.max_time) || config.maxTime
47
+ var maxBytes = Number(query.max_bytes) || config.maxBytes
48
+ var readyEvent = query.ready_event || config.readyEvent
49
+ var loadImages = "load_images" in query || config.loadImages
50
+
51
+ page.settings.loadImages = loadImages
52
+
53
+ page.onInitialized = function() {
54
+ page.evaluate(onInit, readyEvent)
55
+
56
+ function onInit(readyEvent) {
57
+ window.rndrme = true;
58
+ window.addEventListener(readyEvent, function() {
59
+ setTimeout(window.callPhantom, 0)
60
+ })
61
+ }
62
+ }
63
+
64
+ page.onCallback = function() {
65
+ send(200, page.content)
66
+ }
67
+
68
+ var timeout = setTimeout(page.onCallback, maxTime)
69
+
70
+ console.log("(" + port + ") opening page " + href);
71
+
72
+ page.open(href)
73
+
74
+ function send(statusCode, data) {
75
+ clearTimeout(timeout)
76
+
77
+ res.statusCode = statusCode
78
+
79
+ res.setHeader("Content-Type", "text/html")
80
+ res.setHeader("Content-Length", byteLength(data))
81
+ res.setHeader("X-Rndrme-Bytes-Consumed", bytesConsumed.toString())
82
+
83
+ res.write(data)
84
+ res.close()
85
+
86
+ page.close()
87
+ }
88
+ }
89
+
90
+ function byteLength(str) {
91
+ return encodeURIComponent(str).match(/%..|./g).length
92
+ }
93
+
94
+ function toHTML(message) {
95
+ return "<!DOCTYPE html><body>" + message + "</body>\n"
96
+ }
97
+
98
+ function parse(url) {
99
+ var anchor = document.createElement("a")
100
+
101
+ anchor.href = url
102
+ anchor.query = {}
103
+
104
+ anchor.search.slice(1).split("&").forEach(function(pair) {
105
+ pair = pair.split("=").map(decodeURIComponent)
106
+ anchor.query[pair[0]] = pair[1]
107
+ })
108
+
109
+ return anchor
110
+ }
111
+
@@ -3,7 +3,10 @@ upstream unicorn {
3
3
  }
4
4
 
5
5
  upstream phantomjs {
6
- server 127.0.0.1:8020 fail_timeout=0; # 2013-08-01 16:06:02 +0300
6
+ server 127.0.0.1:8000 fail_timeout=0; # 2013-10-21 10:44:21 +0200
7
+ server 127.0.0.1:8000 fail_timeout=0; # 2013-10-21 10:44:21 +0200
8
+ server 127.0.0.1:8012 fail_timeout=0; # 2013-10-21 10:44:21 +0200
9
+ server 127.0.0.1:8013 fail_timeout=0; # 2013-10-21 10:44:21 +0200
7
10
  server 127.0.0.1:8002;
8
11
  server 127.0.0.1:8003;
9
12
  server 127.0.0.1:8004;
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'monitors/response_time'
3
+
4
+ module Monitors
5
+
6
+ describe ResponseTime do
7
+
8
+ subject {ResponseTime}
9
+
10
+ before do
11
+ @process1 = Phantom::Process.new(1, 1, "", 1)
12
+ @process2 = Phantom::Process.new(3, 3, "", 3)
13
+ subject.stub running_instances: [@process1, @process2]
14
+ Cfg.response_time_threshold = 2
15
+ Cfg.response_time_check_retries = 2
16
+ end
17
+
18
+ describe :perform_check do
19
+ context "violating process" do
20
+ before do
21
+ ViolationsRecorders::ResponseTime.stub(:is_violating?).with(@process1).and_return(true)
22
+ ViolationsRecorders::ResponseTime.stub(:is_violating?).with(@process2).and_return(false)
23
+ end
24
+
25
+ it "should restart violating process" do
26
+ Phantom::Manager.should_receive(:restart).with(@process1).once
27
+ subject.perform_check
28
+ end
29
+
30
+ it "should not restart non violating process" do
31
+ Phantom::Manager.should_not_receive(:restart).with(@process2)
32
+ subject.perform_check
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'monitors/violations_recorders/response_time'
3
+
4
+ module Monitors
5
+ module ViolationsRecorders
6
+
7
+ RESPONSE_TIME_THRESHOLD = 2
8
+
9
+ describe ResponseTime do
10
+
11
+ subject {ResponseTime}
12
+
13
+ describe :process_is_violating? do
14
+
15
+ before do
16
+ Cfg.stub(:response_time_threshold).and_return(RESPONSE_TIME_THRESHOLD)
17
+ end
18
+
19
+ context "timeout" do
20
+ before do
21
+ subject.stub(:check_response_time).and_raise(Timeout::Error)
22
+ end
23
+
24
+ it "should return true" do
25
+ subject.process_is_violating?(stub).should be_true
26
+ end
27
+ end
28
+
29
+ context "fast response time" do
30
+ before do
31
+ subject.stub(:check_response_time).and_return(RESPONSE_TIME_THRESHOLD - 1)
32
+ end
33
+
34
+ it "should return false" do
35
+ subject.process_is_violating?(stub).should be_false
36
+ end
37
+ end
38
+
39
+ context "slow response time" do
40
+ before do
41
+ subject.stub(:check_response_time).and_return(RESPONSE_TIME_THRESHOLD + 1)
42
+ end
43
+
44
+ it "should return true" do
45
+ subject.process_is_violating?(stub).should be_true
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phantom-manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erez Rabih
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-01 00:00:00.000000000 Z
11
+ date: 2013-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -73,10 +73,12 @@ files:
73
73
  - lib/monitors/base.rb
74
74
  - lib/monitors/memory.rb
75
75
  - lib/monitors/processes.rb
76
+ - lib/monitors/response_time.rb
76
77
  - lib/monitors/restart_listener.rb
77
78
  - lib/monitors/violations_recorders/base.rb
78
79
  - lib/monitors/violations_recorders/memory.rb
79
80
  - lib/monitors/violations_recorders/processes.rb
81
+ - lib/monitors/violations_recorders/response_time.rb
80
82
  - lib/nginx/manager.rb
81
83
  - lib/phantom/.DS_Store
82
84
  - lib/phantom/collector.rb
@@ -87,6 +89,7 @@ files:
87
89
  - lib/utils/limited_array.rb
88
90
  - lib/utils/lock.rb
89
91
  - lib/utils/logger.rb
92
+ - lib/utils/rndrme.js
90
93
  - lib/utils/shell.rb
91
94
  - phantom-manager.gemspec
92
95
  - spec/files/config.yml
@@ -94,10 +97,12 @@ files:
94
97
  - spec/lib/monitors/base_spec.rb
95
98
  - spec/lib/monitors/memory_spec.rb
96
99
  - spec/lib/monitors/processes_spec.rb
100
+ - spec/lib/monitors/response_time_spec.rb
97
101
  - spec/lib/monitors/restart_listener_spec.rb
98
102
  - spec/lib/monitors/violations_recorders/base_spec.rb
99
103
  - spec/lib/monitors/violations_recorders/memory_spec.rb
100
104
  - spec/lib/monitors/violations_recorders/processes_spec.rb
105
+ - spec/lib/monitors/violations_recorders/response_time_spec.rb
101
106
  - spec/lib/nginx/manager_spec.rb
102
107
  - spec/lib/phantom/collector_spec.rb
103
108
  - spec/lib/phantom/manager_spec.rb
@@ -138,10 +143,12 @@ test_files:
138
143
  - spec/lib/monitors/base_spec.rb
139
144
  - spec/lib/monitors/memory_spec.rb
140
145
  - spec/lib/monitors/processes_spec.rb
146
+ - spec/lib/monitors/response_time_spec.rb
141
147
  - spec/lib/monitors/restart_listener_spec.rb
142
148
  - spec/lib/monitors/violations_recorders/base_spec.rb
143
149
  - spec/lib/monitors/violations_recorders/memory_spec.rb
144
150
  - spec/lib/monitors/violations_recorders/processes_spec.rb
151
+ - spec/lib/monitors/violations_recorders/response_time_spec.rb
145
152
  - spec/lib/nginx/manager_spec.rb
146
153
  - spec/lib/phantom/collector_spec.rb
147
154
  - spec/lib/phantom/manager_spec.rb