instrumental_tools 0.6.0 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 696839b8867194c4d531c7bda9bcaf10dd17072d
4
- data.tar.gz: 62895095b5e50f81e6e7521ab35e2afc6d001082
3
+ metadata.gz: bad5b9d397d58baf15de2745da956ae559549882
4
+ data.tar.gz: 8b09e4a169f43649be004e1ba7c00b044540da1a
5
5
  SHA512:
6
- metadata.gz: 3635747abde5adca183e7a9ed0cd81216de45e692c3ee5bf5ad14bdc394aadd968831b0c9e1273e13df068f33699aced51453495ad8e6016779bcfbe773f89cd
7
- data.tar.gz: af4ce98961f585bdb850c5ddd03290f86970d5ac91c53b2aade523b70031e39d31034c91c964a93c2ee73e2fce4f8a8d30a4d44c6c410571375bb704e9e7278e
6
+ metadata.gz: 449032b89546476bc4a0a3dfcd4c923e03b498c9141be811a30c1104b3b1035e953a5bc949dbb59449216e64491aa7ad531044c5d9389d7107dddbe0916913e3
7
+ data.tar.gz: 4f139e836b4353827842db0f9cac6b8faea668c95e1dd57a25939a90006221b5e2c7d166cbcef53b495bca887268b82c8eaa87900283f849b86b194f97f6fa0d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
+ ### Unreleased
2
+ * Configurable pid and log file locations
3
+ * Pid and log file default to $HOME
4
+ * Process control commands do not require API key
5
+ * Omit "-d" in favor of "start" and "stop", "foreground" runs process in foreground
6
+ * Configurable reporting interval
7
+ * Custom scripts may be executed and have their output sent to Instrumental (See [CUSTOM_METRICS.md](CUSTOM_METRICS.md))
8
+
1
9
  ### 0.6.0 [August 11th, 2014]
2
- * Upgraded instrumental_agent gem to the latest version
10
+ * Don't report swap usage if it's zero (Patrick Wyatt)
3
11
 
4
12
  ### 0.5.8 [August 11th, 2014]
5
13
  * Upgraded instrumental_agent gem to the latest version
data/CUSTOM_METRICS.md ADDED
@@ -0,0 +1,56 @@
1
+ # Custom Metric Scripts
2
+
3
+ You may have specific pieces of architecture that you would like `instrument_server` to monitor. As of version 0.7.0 of the `instrument_server` gem, you may pass the `-e` flag to `instrument_server` on startup to enable this functionality. There are several [examples](examples/) of scripts that you may use for your infrastructure, or you can [write your own](#writing_custom_scripts).
4
+
5
+ ## Installing Custom Scripts
6
+
7
+ To install custom scripts, place them in the directory `$HOME/.instrumental_scripts`. The `instrument_server` process will create this directory if it doesn't exist the first time you run the process with script functionality enabled (`-e`). You may also specify a specific directory to the `instrument_server` process with the `-s` (`--script-location`) flag.
8
+
9
+ ### Security
10
+
11
+
12
+ The directory you use for custom scripts must be readable/writable only by owner (`0700`), which must be the same user that the `instrument_server` process runs as. The `instrument_server` process will exit with an error message alerting you to the fact that it cannot use the directory otherwise.
13
+
14
+ Additionally, all scripts to be ran by the `instrument_server` process must be readable/writable only by the user the process is executing as (`0700`).
15
+
16
+ ## <a name="writing_custom_scripts"></a> Writing Custom Scripts
17
+
18
+ A custom script may be a binary or shell script that exists in the custom scripts directory (`$HOME/.instrumental_scripts`). Each time the `instrument_server` process collects system metrics, it will also execute your script with the following arguments:
19
+
20
+ * Argument 1: The Unix timestamp of the last time this script had been executed, in seconds. If the process has not successfully been run by `instrument_server` before, this value will be 0.
21
+ * Argument 2: The exit status of the process the last time this script had been executed. If the process has not successfully ran by `instrument_server` before, this value will not be present.
22
+ * `STDIN`: The `STDIN` pipe to your process will contain the output of your script the last time it had been executed. You may use this data to compute differences between the last time your script ran and the current execution. (_The [MySQL example](examples/mysql/mysql_status.rb) uses this to compute rate metrics_)
23
+ * Environment: Any environment variables set for the `instrument_server` process will be available to your process.
24
+
25
+ Your script is expected to output data in the following format on `STDOUT` in order to be sent back to Instrumental:
26
+
27
+ ```
28
+ METRIC_NAME METRIC_VALUE
29
+ ```
30
+
31
+ For example, if a script named `application_load` were to report two metrics, `memory` and `load`, to the `instrument_server` process, its output should be:
32
+
33
+ ```
34
+ memory 1024.0
35
+ load 0.7
36
+ ```
37
+
38
+ The `instrument_server` process will submit each metric to Instrumental in the following form:
39
+
40
+ ```
41
+ HOST_NAME.SCRIPT_NAME.METRIC_NAME
42
+ ```
43
+
44
+ Using the previous example, if the `application_load` script ran on a host named `app-0001`, its `memory` and `load` metrics would be submitted to Instrumental as `app-0001.application_load.memory` and `app-0001.application_load.load`.
45
+
46
+ ### Exit Codes
47
+
48
+ If you do not want the output of your script submitted to Instrumental, your process should exit with a non-zero exit code. Its `STDOUT` output will still be provided to your script on the next iteration.
49
+
50
+ ### Errors
51
+
52
+ You may output error information on `STDERR` of your process, and it will be output to the `instrument_server` log to aid in debugging script behavior.
53
+
54
+ ### Timeouts
55
+
56
+ Your script is responsible for managing timeouts. The `instrument_server` process will not attempt to terminate your process for you.
data/README.md CHANGED
@@ -25,15 +25,22 @@ instrument_server -k <API_KEY> -H <HOSTNAME>
25
25
  To start instrument_server as a daemon:
26
26
 
27
27
  ```
28
- instrument_server -k <API_KEY> -d
28
+ instrument_server -k <API_KEY> start
29
29
  ```
30
30
 
31
- While the -d flag alone is sufficient for starting instrument_server as a daemon, use of additional parameters allows interaction with the running daemon process:
31
+ The `start` command will start and detach the process. You may issue additional commands to the process like:
32
32
 
33
- * start, stop, restart - run, halt, or restart the daemon
34
- * status - display daemon status (running, stopped)
35
- * clean - remove any files created by the daemon
36
- * kill - forcibly halt the daemon and remove its pid file
33
+ * `start` - start and detach the process
34
+ * `stop` - stop the currently running `instrument_server` process
35
+ * `restart` - restart the currently running `instrument_server` process
36
+ * `foreground` - run the process in the foreground instead of detaching
37
+ * `status` - display daemon status (running, stopped)
38
+ * `clean` - remove any files created by the daemon
39
+ * `kill` - forcibly halt the daemon and remove its pid file
40
+
41
+ ### Custom Metrics
42
+
43
+ You can create custom scripts whose output will be sent to Instrumental every time `instrument_server` checks in. You can read more about how to create these scripts at [CUSTOM_METRICS.md](CUSTOM_METRICS.md).
37
44
 
38
45
 
39
46
  ### Capistrano Integration
@@ -8,443 +8,169 @@ rescue Gem::LoadError
8
8
  puts ' gem install instrumental_agent'
9
9
  exit 1
10
10
  end
11
+ require 'etc'
11
12
  require 'instrumental_agent'
12
- require 'pidly'
13
- require 'tmpdir'
13
+ require 'fileutils'
14
14
  require 'optparse'
15
15
  require 'socket'
16
16
  $: << File.join(File.dirname(__FILE__), "..", "lib")
17
17
  require 'instrumental_tools/version'
18
+ require 'instrumental_tools/server_controller'
18
19
 
19
- class SystemInspector
20
- TYPES = [:gauges, :incrementors]
21
- attr_accessor *TYPES
22
-
23
- def self.memory
24
- @memory ||= Memory.new
25
- end
26
-
27
- def initialize
28
- @gauges = {}
29
- @incrementors = {}
30
- @platform =
31
- case RUBY_PLATFORM
32
- when /linux/
33
- Linux
34
- when /darwin/
35
- OSX
36
- else
37
- raise "unsupported OS"
38
- end
20
+ def require_api_key(options, parser)
21
+ if options[:api_key].to_s.strip.empty?
22
+ print parser.help
23
+ exit 1
39
24
  end
25
+ end
40
26
 
41
- def self.command_missing(command, section)
42
- puts "Command #{command} not found. Metrics for #{section} will not be collected."
43
- end
27
+ default_script_directory = File.join(Dir.home, '.instrumental_scripts')
28
+ default_command = :foreground
44
29
 
45
- def self.command_present?(command, section)
46
- `which #{command}`.length > 0 || command_missing(command, section)
47
- end
30
+ options = {
31
+ :collector => 'collector.instrumentalapp.com',
32
+ :port => '8000',
33
+ :hostname => Socket.gethostname,
34
+ :pid_location => File.join(Dir.home, 'instrument_server.pid'),
35
+ :log_location => File.join(Dir.home, 'instrument_server.log'),
36
+ :enable_scripts => false,
37
+ :script_location => default_script_directory,
38
+ :report_interval => 30,
39
+ :debug => false
40
+ }
48
41
 
49
- def load_all
50
- self.class.memory.cycle
42
+ option_parser = OptionParser.new do |opts|
43
+ opts.banner = <<-EOBANNER
44
+ Usage: instrument_server -k API_KEY [options] [#{ServerController::COMMANDS.join('|')}]"
45
+ Default command: #{default_command.to_s}
46
+ EOBANNER
51
47
 
52
- load @platform.load_cpu
53
- load @platform.load_memory
54
- load @platform.load_disks
55
- load @platform.load_filesystem
48
+ opts.on('-k', '--api_key API_KEY', 'API key of your project') do |api_key|
49
+ options[:api_key] = api_key
56
50
  end
57
51
 
58
- def load(stats)
59
- @gauges.merge!(stats[:gauges] || {})
52
+ opts.on('-c', '--collector COLLECTOR[:PORT]', "Collector (default #{options[:collector]}:#{options[:port]})") do |collector|
53
+ address, port = collector.split(':')
54
+ options[:collector] = address
55
+ options[:port] = port if port
60
56
  end
61
57
 
62
- module OSX
63
- def self.load_cpu
64
- output = { :gauges => {} }
65
- if SystemInspector.command_present?('top', 'CPU')
66
- output[:gauges].merge!(top)
67
- end
68
- output
69
- end
70
-
71
- def self.top
72
- lines = []
73
- processes = date = load = cpu = nil
74
- IO.popen('top -l 1 -n 0') do |top|
75
- processes = top.gets.split(': ')[1]
76
- date = top.gets
77
- load = top.gets.split(': ')[1]
78
- cpu = top.gets.split(': ')[1]
79
- end
80
-
81
- user, system, idle = cpu.split(", ").map { |v| v.to_f }
82
- load1, load5, load15 = load.split(", ").map { |v| v.to_f }
83
- total, running, stuck, sleeping, threads = processes.split(", ").map { |v| v.to_i }
84
-
85
- {
86
- 'cpu.user' => user,
87
- 'cpu.system' => system,
88
- 'cpu.idle' => idle,
89
- 'load.1min' => load1,
90
- 'load.5min' => load5,
91
- 'load.15min' => load15,
92
- 'processes.total' => total,
93
- 'processes.running' => running,
94
- 'processes.stuck' => stuck,
95
- 'processes.sleeping' => sleeping,
96
- 'threads' => threads,
97
- }
98
- end
99
-
100
- def self.load_memory
101
- # TODO: swap
102
- output = { :gauges => {} }
103
- if SystemInspector.command_present?('vm_stat', 'memory')
104
- output[:gauges].merge!(vm_stat)
105
- end
106
- output
107
- end
108
-
109
- def self.vm_stat
110
- header, *rows = `vm_stat`.split("\n")
111
- page_size = header.match(/page size of (\d+) bytes/)[1].to_i
112
- sections = ["free", "active", "inactive", "wired", "speculative", "wired down"]
113
- output = {}
114
- total = 0.0
115
- rows.each do |row|
116
- if match = row.match(/Pages (.*):\s+(\d+)\./)
117
- section, value = match[1, 2]
118
- if sections.include?(section)
119
- value = value.to_f * page_size / 1024 / 1024
120
- output["memory.#{section.gsub(' ', '_')}_mb"] = value
121
- total += value
122
- end
123
- end
124
- end
125
- output["memory.free_percent"] = output["memory.free_mb"] / total * 100 # TODO: verify
126
- output
127
- end
128
-
129
- def self.load_disks
130
- output = { :gauges => {} }
131
- if SystemInspector.command_present?('df', 'disk')
132
- output[:gauges].merge!(df)
133
- end
134
- output
135
- end
136
-
137
- def self.df
138
- output = {}
139
- `df -k`.split("\n").grep(%r{^/dev/}).each do |line|
140
- device, total, used, available, capacity, mount = line.split(/\s+/)
141
- names = [File.basename(device)]
142
- names << 'root' if mount == '/'
143
- names.each do |name|
144
- output["disk.#{name}.total_mb"] = total.to_f / 1024
145
- output["disk.#{name}.used_mb"] = used.to_f / 1024
146
- output["disk.#{name}.available_mb"] = available.to_f / 1024
147
- output["disk.#{name}.available_percent"] = available.to_f / total.to_f * 100
148
- end
149
- end
150
- output
151
- end
152
-
153
- def self.netstat(interface = 'en1')
154
- # mostly functional network io stats
155
- headers, *lines = `netstat -ibI #{interface}`.split("\n").map { |l| l.split(/\s+/) } # FIXME: vulnerability?
156
- headers = headers.map { |h| h.downcase }
157
- lines.each do |line|
158
- if !line[3].include?(':')
159
- return Hash[headers.zip(line)]
160
- end
161
- end
162
- end
163
-
164
- def self.load_filesystem
165
- {}
166
- end
58
+ opts.on('-H', '--hostname HOSTNAME', "Hostname to report as (default #{options[:hostname]})") do |hostname|
59
+ options[:hostname] = hostname
167
60
  end
168
61
 
169
- module Linux
170
- def self.load_cpu
171
- output = { :gauges => {} }
172
- output[:gauges].merge!(cpu)
173
- output[:gauges].merge!(loadavg)
174
- output
175
- end
176
-
177
- def self.cpu
178
- categories = [:user, :nice, :system, :idle, :iowait]
179
- values = `cat /proc/stat | grep cpu[^0-9]`.chomp.split(/\s+/).slice(1, 5).map { |v| v.to_f }
180
- SystemInspector.memory.store(:cpu_values, values.dup)
181
- if previous_values = SystemInspector.memory.retrieve(:cpu_values)
182
- index = -1
183
- values.collect!{ |value| (previous_values[index += 1] - value).abs }
184
- end
185
- data = Hash[*categories.zip(values).flatten]
186
- total = values.inject { |memo, value| memo + value }
187
-
188
- output = {}
189
- if previous_values
190
- data.each do |category, value|
191
- output["cpu.#{category}"] = value / total * 100
192
- end
193
- end
194
- output["cpu.in_use"] = 100 - data[:idle] / total * 100
195
- output
196
- end
197
-
198
- def self.loadavg
199
- min_1, min_5, min_15 = `cat /proc/loadavg`.split(/\s+/)
200
- {
201
- 'load.1min' => min_1.to_f,
202
- 'load.5min' => min_5.to_f,
203
- 'load.15min' => min_15.to_f,
204
- }
205
- end
206
-
207
- def self.load_memory
208
- output = { :gauges => {} }
209
- if SystemInspector.command_present?('free', 'memory')
210
- output[:gauges].merge!(memory)
211
- end
212
- if SystemInspector.command_present?('free', 'swap')
213
- output[:gauges].merge!(swap)
214
- end
215
- output
216
- end
217
-
218
- def self.memory
219
- _, total, used, free, shared, buffers, cached = `free -k -o | grep Mem`.chomp.split(/\s+/)
220
- {
221
- 'memory.used_mb' => used.to_f / 1024,
222
- 'memory.free_mb' => free.to_f / 1024,
223
- 'memory.buffers_mb' => buffers.to_f / 1024,
224
- 'memory.cached_mb' => cached.to_f / 1024,
225
- 'memory.free_percent' => (free.to_f / total.to_f) * 100,
226
- }
227
- end
228
-
229
- def self.swap
230
- _, total, used, free = `free -k -o | grep Swap`.chomp.split(/\s+/)
231
- return {} if total.to_i == 0
232
- {
233
- 'swap.used_mb' => used.to_f / 1024,
234
- 'swap.free_mb' => free.to_f / 1024,
235
- 'swap.free_percent' => (free.to_f / total.to_f) * 100,
236
- }
237
- end
62
+ opts.on('-p', '--pid LOCATION', "Where daemon PID file is located (default #{options[:pid_location]})") do |pid_location|
63
+ options[:pid_location] = pid_location
64
+ end
238
65
 
239
- def self.load_disks
240
- output = { :gauges => {} }
241
- if SystemInspector.command_present?('df', 'disk storage')
242
- output[:gauges].merge!(disk_storage)
243
- end
244
- if SystemInspector.command_present?('mount', 'disk IO')
245
- output[:gauges].merge!(disk_io)
246
- end
247
- output
248
- end
66
+ opts.on('-l', '--log LOCATION', "Where to put the instrument_server log file (default #{options[:log_location]})") do |log_location|
67
+ options[:log_location] = log_location
68
+ end
249
69
 
250
- def self.disk_storage
251
- output = {}
252
- `df -Pka`.split("\n").each do |line|
253
- device, total, used, available, capacity, mount = line.split(/\s+/)
254
- if device == "tmpfs"
255
- names = ["tmpfs_#{mount.gsub(/[^[:alnum:]]/, "_")}".gsub(/_+/, "_")]
256
- elsif device =~ %r{/dev/}
257
- names = [File.basename(device)]
258
- else
259
- next
260
- end
261
- names << 'root' if mount == '/'
262
- names.each do |name|
263
- output["disk.#{name}.total_mb"] = total.to_f / 1024
264
- output["disk.#{name}.used_mb"] = used.to_f / 1024
265
- output["disk.#{name}.available_mb"] = available.to_f / 1024
266
- output["disk.#{name}.available_percent"] = available.to_f / total.to_f * 100
267
- end
268
- end
269
- output
270
- end
70
+ opts.on('-r', '--report-interval INTERVAL_IN_SECONDS', "How often to report metrics to Instrumental (default #{options[:report_interval]})") do |interval|
71
+ options[:report_interval] = interval.to_i
72
+ end
271
73
 
272
- def self.disk_io
273
- mounted_devices = `mount`.split("\n").grep(/^\/dev\/(\w+)/) { $1 }
274
- diskstats_lines = `cat /proc/diskstats`.split("\n").grep(/#{mounted_devices.join('|')}/)
275
- entries = diskstats_lines.map do |line|
276
- values = line.split
277
- entry = {}
278
- entry[:time] = Time.now
279
- entry[:device] = values[2]
280
- entry[:utilization] = values[12].to_f
281
- SystemInspector.memory.store("disk_stats_#{entry[:device]}".to_sym, entry)
282
- end
283
-
284
- output = {}
285
- entries.each do |entry|
286
- if previous_entry = SystemInspector.memory.retrieve("disk_stats_#{entry[:device]}".to_sym)
287
- time_delta = (entry[:time] - previous_entry[:time]) * 1000
288
- utilization_delta = entry[:utilization] - previous_entry[:utilization]
289
- output["disk.#{entry[:device]}.percent_utilization"] = utilization_delta / time_delta * 100
290
- end
291
- end
292
- output
293
- end
74
+ opts.on('-e', '--enable-scripts', "Enable custom metric gathering from local scripts (default #{options[:enable_scripts]})") do
75
+ options[:enable_scripts] = true
76
+ end
294
77
 
295
- def self.load_filesystem
296
- output = { :gauges => {} }
297
- if SystemInspector.command_present?('sysctl', 'filesystem')
298
- output[:gauges].merge!(filesystem)
299
- end
300
- output
301
- end
78
+ opts.on('-s', '--script-location PATH_TO_DIRECTORY', "Directory where local scripts for custom metrics are located (default #{options[:script_location]})") do |path|
79
+ options[:script_location] = path
80
+ end
302
81
 
303
- def self.filesystem
304
- allocated, unused, max = `sysctl fs.file-nr`.split[-3..-1].map { |v| v.to_i }
305
- open_files = allocated - unused
306
- { 'filesystem.open_files' => open_files,
307
- 'filesystem.max_open_files' => max,
308
- 'filesystem.open_files_pct_max' => (open_files.to_f / max.to_f) * 100,
309
- }
310
- end
82
+ opts.on('--debug', "Print all sent metrics to the log") do
83
+ options[:debug] = true
311
84
  end
312
85
 
313
- class Memory
314
- attr_reader :past_values, :current_values
86
+ opts.on('-h', '--help', 'Display this screen') do
87
+ puts opts
88
+ exit
89
+ end
90
+ end
315
91
 
316
- def initialize
317
- @past_values = {}
318
- @current_values = {}
319
- end
92
+ option_parser.parse!
320
93
 
321
- def store(attribute, value)
322
- @current_values[attribute] = value
323
- end
94
+ command = ARGV.first && ARGV.first.to_sym
95
+ command ||= default_command
324
96
 
325
- def retrieve(attribute)
326
- @past_values[attribute]
327
- end
97
+ options[:api_key] ||= ENV["INSTRUMENTAL_TOKEN"]
328
98
 
329
- def cycle
330
- @past_values = @current_values
331
- @current_values = {}
332
- end
333
- end
99
+ if options[:pid_location].to_s.strip.empty?
100
+ raise "You must provide a valid path for the PID file (-p PID_PATH)"
101
+ end
334
102
 
103
+ if !File.directory?(File.dirname(options[:pid_location]))
104
+ raise "The directory specified for the pid file #{options[:pid_location]} does not exist, please create"
335
105
  end
336
106
 
337
- class ServerController < Pidly::Control
338
- COMMANDS = [:start, :stop, :status, :restart, :clean, :kill]
339
- REPORT_INTERVAL = 60
107
+ if options[:log_location].to_s.strip.empty?
108
+ raise "You must provide a valid path for the log file (-l LOG_PATH)"
109
+ end
340
110
 
341
- attr_accessor :run_options
111
+ if !File.directory?(File.dirname(options[:log_location]))
112
+ raise "The directory specified for the log file #{options[:log_location]} does not exist, please create"
113
+ end
342
114
 
343
- before_start do
344
- puts "Starting daemon process: #{@pid}"
345
- end
115
+ if options[:report_interval].to_i < 1
116
+ raise "You must specify a reporting interval greater than 0"
117
+ end
346
118
 
347
- start :run
119
+ if options[:enable_scripts]
348
120
 
349
- stop do
350
- puts "Attempting to kill daemon process: #{@pid}"
121
+ if options[:script_location].to_s.strip.empty?
122
+ raise "You must specify a valid directory to execute custom scripts in."
351
123
  end
352
124
 
353
- # after_stop :something
354
-
355
- error do
356
- puts 'Error encountered'
125
+ if options[:script_location] == default_script_directory
126
+ FileUtils.mkdir_p(default_script_directory, :mode => 0700)
357
127
  end
358
128
 
359
- def self.run(options)
360
- agent = Instrumental::Agent.new(options[:api_key], :collector => [options[:collector], options[:port]].compact.join(':'))
361
- puts "insrument_server version #{Instrumental::Tools::VERSION} started at #{Time.now.utc}"
362
- puts "Collecting stats under the hostname: #{options[:hostname]}"
363
- loop do
364
- t = Time.now.to_i
365
- next_run_at = (t - t % REPORT_INTERVAL) + REPORT_INTERVAL
366
- sleep [next_run_at - t, 0].max
367
- inspector = SystemInspector.new
368
- inspector.load_all
369
- inspector.gauges.each do |stat, value|
370
- agent.gauge("#{options[:hostname]}.#{stat}", value)
371
- end
372
- # agent.increment("#{host}.#{stat}", delta)
373
- end
129
+ if !File.directory?(options[:script_location])
130
+ raise "The directory #{options[:script_location]} does not exist."
374
131
  end
375
132
 
376
- def initialize(options={})
377
- @run_options = options.delete(:run_options) || {}
378
- super(options)
379
- end
133
+ stat = File::Stat.new(File.expand_path(options[:script_location]))
380
134
 
381
- def run
382
- self.class.run(run_options)
135
+ if !stat.owned? || ((stat.mode & 0xFFF) ^ 0O700) != 0
136
+ uid = Process.uid
137
+ username = Etc.getpwuid(uid).name
138
+ raise "The directory #{options[:script_location]} is writable/readable by others. Please ensure it is only writable / readable by user/uid #{username}/#{uid}"
383
139
  end
384
140
 
385
- alias_method :clean, :clean!
386
141
  end
387
142
 
388
- def require_api_key(options, parser)
389
- unless options[:api_key] # present?
390
- print parser.help
391
- exit 1
392
- end
143
+ if [:start, :restart, :foreground].include?(command)
144
+ require_api_key(options, option_parser)
393
145
  end
394
146
 
395
- options = {
396
- :daemon => false,
397
- :collector => 'collector.instrumentalapp.com',
398
- :port => '8000',
399
- :hostname => Socket.gethostname,
400
- }
147
+ running_as_daemon = [:start, :restart].include?(command)
401
148
 
402
- option_parser = OptionParser.new do |opts|
403
- opts.banner = "Usage: instrument_server -k API_KEY [options] [-d #{ServerController::COMMANDS.join('|')}]"
404
- opts.on('-k', '--api_key API_KEY', 'API key of your project') do |api_key|
405
- options[:api_key] = api_key
406
- end
407
- opts.on('-c', '--collector COLLECTOR[:PORT]', "Collector (default #{options[:collector]}:#{options[:port]})") do |collector|
408
- address, port = collector.split(':')
409
- options[:collector] = address
410
- options[:port] = port if port
411
- end
412
- opts.on('-H', '--hostname HOSTNAME', "Hostname to report as (default #{options[:hostname]})") do |hostname|
413
- options[:hostname] = hostname
414
- end
415
- opts.on('-d', '--daemonize', 'Run as daemon') do
416
- options[:daemon] = true
417
- end
418
- opts.on('-h', '--help', 'Display this screen') do
419
- puts opts
420
- exit
421
- end
149
+ controller = ServerController.spawn(
150
+ :name => File.basename(__FILE__),
151
+ :path => Dir.pwd,
152
+ :pid_file => options[:pid_location],
153
+ :verbose => true,
154
+ :log_file => options[:log_location],
155
+ :run_options => options.merge(:daemon => running_as_daemon)
156
+ )
157
+
158
+ if ServerController::COMMANDS.include?(command)
159
+ controller.send command
160
+ else
161
+ raise "Command must be one of: #{ServerController::COMMANDS.join(', ')}"
422
162
  end
423
163
 
424
- option_parser.parse!
425
- options[:api_key] ||= ENV["INSTRUMENTAL_TOKEN"]
426
164
 
427
- if options.delete(:daemon)
428
- @server_controller = ServerController.spawn(
429
- :name => 'instrument_server',
430
- :path => Dir.tmpdir,
431
- :pid_file => File.join(Dir.tmpdir, 'pids', 'instrument_server.pid'),
432
- :verbose => true,
433
- # :signal => 'kill',
434
- :log_file => File.join(Dir.tmpdir, 'log', 'instrument_server.log'),
435
- :run_options => options
436
- )
437
- command = ARGV.first && ARGV.first.to_sym
438
- command ||= :start
439
- if [:start, :restart].include?(command)
440
- require_api_key(options, option_parser)
165
+
166
+ if running_as_daemon
167
+ begin
168
+ Timeout.timeout(5) do
169
+ Process.waitpid(controller.pid)
170
+ end
171
+ rescue Timeout::Error
441
172
  end
442
- if ServerController::COMMANDS.include?(command)
443
- @server_controller.send command
444
- else
445
- puts "Command must be one of: #{ServerController::COMMANDS.join(', ')}"
173
+ if !controller.running?
174
+ raise "Failed to start process, see #{options[:log_location]}"
446
175
  end
447
- else
448
- require_api_key(options, option_parser)
449
- ServerController.run(options)
450
176
  end