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 +4 -4
- data/CHANGELOG.md +9 -1
- data/CUSTOM_METRICS.md +56 -0
- data/README.md +13 -6
- data/bin/instrument_server +111 -385
- data/examples/README.md +7 -0
- data/examples/docker/README.md +20 -0
- data/examples/docker/docker.rb +74 -0
- data/examples/mongo/README.md +25 -0
- data/examples/mongo/mongo_3.rb +85 -0
- data/examples/mysql/README.md +13 -0
- data/examples/mysql/mysql_status.rb +88 -0
- data/instrumental_tools.gemspec +3 -1
- data/lib/instrumental_tools/metric_script_executor.rb +96 -0
- data/lib/instrumental_tools/server_controller.rb +70 -0
- data/lib/instrumental_tools/system_inspector/linux.rb +146 -0
- data/lib/instrumental_tools/system_inspector/osx.rb +114 -0
- data/lib/instrumental_tools/system_inspector.rb +67 -0
- data/lib/instrumental_tools/version.rb +1 -1
- metadata +18 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bad5b9d397d58baf15de2745da956ae559549882
|
4
|
+
data.tar.gz: 8b09e4a169f43649be004e1ba7c00b044540da1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
*
|
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>
|
28
|
+
instrument_server -k <API_KEY> start
|
29
29
|
```
|
30
30
|
|
31
|
-
|
31
|
+
The `start` command will start and detach the process. You may issue additional commands to the process like:
|
32
32
|
|
33
|
-
* start
|
34
|
-
*
|
35
|
-
*
|
36
|
-
*
|
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
|
data/bin/instrument_server
CHANGED
@@ -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 '
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
42
|
-
|
43
|
-
end
|
27
|
+
default_script_directory = File.join(Dir.home, '.instrumental_scripts')
|
28
|
+
default_command = :foreground
|
44
29
|
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
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
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
304
|
-
|
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
|
-
|
314
|
-
|
86
|
+
opts.on('-h', '--help', 'Display this screen') do
|
87
|
+
puts opts
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
end
|
315
91
|
|
316
|
-
|
317
|
-
@past_values = {}
|
318
|
-
@current_values = {}
|
319
|
-
end
|
92
|
+
option_parser.parse!
|
320
93
|
|
321
|
-
|
322
|
-
|
323
|
-
end
|
94
|
+
command = ARGV.first && ARGV.first.to_sym
|
95
|
+
command ||= default_command
|
324
96
|
|
325
|
-
|
326
|
-
@past_values[attribute]
|
327
|
-
end
|
97
|
+
options[:api_key] ||= ENV["INSTRUMENTAL_TOKEN"]
|
328
98
|
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
115
|
+
if options[:report_interval].to_i < 1
|
116
|
+
raise "You must specify a reporting interval greater than 0"
|
117
|
+
end
|
346
118
|
|
347
|
-
|
119
|
+
if options[:enable_scripts]
|
348
120
|
|
349
|
-
|
350
|
-
|
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
|
-
|
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
|
-
|
360
|
-
|
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
|
-
|
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
|
-
|
382
|
-
|
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
|
-
|
389
|
-
|
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
|
-
|
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
|
-
|
403
|
-
|
404
|
-
|
405
|
-
options[:
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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
|
443
|
-
|
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
|