custodian 0.0.1 → 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.
- data/LICENSE +21 -0
- data/README.md +28 -12
- data/Rakefile +6 -0
- data/bin/custodian +5 -0
- data/custodian.gemspec +4 -2
- data/docs/images/example.png +0 -0
- data/lib/custodian.rb +5 -3
- data/lib/custodian/api.rb +33 -0
- data/lib/custodian/cli.rb +158 -0
- data/lib/custodian/os.rb +25 -0
- data/lib/custodian/samplers.rb +100 -0
- data/lib/custodian/samplers/darwin/cpu.rb +19 -0
- data/lib/custodian/samplers/darwin/load.rb +19 -0
- data/lib/custodian/samplers/darwin/ram.rb +29 -0
- data/lib/custodian/samplers/darwin/who.rb +13 -0
- data/lib/custodian/samplers/linux/cpu.rb +29 -0
- data/lib/custodian/samplers/linux/load.rb +19 -0
- data/lib/custodian/samplers/linux/ram.rb +21 -0
- data/lib/custodian/samplers/sampler.rb +58 -0
- data/lib/custodian/samplers/utilities.rb +37 -0
- data/lib/custodian/version.rb +1 -1
- data/test/cli_test.rb +24 -0
- data/test/sampler_test.rb +23 -0
- data/test/samplers_test.rb +47 -0
- metadata +72 -5
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2012 Johannes Gorset
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,23 +1,39 @@
|
|
1
1
|
# Custodian
|
2
2
|
|
3
|
-
Custodian is a lightweight resource monitor that
|
3
|
+
Custodian is a lightweight resource monitor that makes it really easy
|
4
|
+
and really awesome to sample metrics.
|
4
5
|
|
5
6
|
## Usage
|
6
7
|
|
7
|
-
|
8
|
-
them on port 5100:
|
8
|
+
### Server
|
9
9
|
|
10
|
-
|
10
|
+
Start Custodian and configure it to expose its metrics on port 5100:
|
11
11
|
|
12
|
-
|
12
|
+
$ custodian --port=5100
|
13
13
|
|
14
|
-
|
14
|
+

|
15
15
|
|
16
|
-
|
16
|
+
#### Samplers
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
18
|
+
Custodian aggregates metrics from *samplers*, and ships with samplers for popular figures
|
19
|
+
such as logged in users and load averages. That's interesting and all, but custom
|
20
|
+
samplers are all the rage.
|
22
21
|
|
23
|
-
|
22
|
+
```ruby
|
23
|
+
class RegisteredUsers < Custodian::Samplers::Sampler
|
24
|
+
describe "Registered users"
|
25
|
+
|
26
|
+
def self.sample
|
27
|
+
Users.count
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
You can load your own samplers with the `--samplers` option.
|
33
|
+
|
34
|
+
$ custodian --samplers=~/.samplers
|
35
|
+
|
36
|
+
### Clients
|
37
|
+
|
38
|
+
Unless you're crazy about JSON, you'll want to consume Custodian's API with a
|
39
|
+
*client*. Unfortunately, there are no clients for Custodian yet — you should make one!
|
data/Rakefile
CHANGED
data/bin/custodian
ADDED
data/custodian.gemspec
CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
|
23
|
-
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "thin"
|
24
|
+
s.add_runtime_dependency "rack"
|
25
|
+
s.add_runtime_dependency "active_support"
|
24
26
|
end
|
Binary file
|
data/lib/custodian.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require "custodian/version"
|
2
|
-
|
3
1
|
module Custodian
|
4
|
-
|
2
|
+
autoload :VERSION, "custodian/version"
|
3
|
+
autoload :Samplers, "custodian/samplers"
|
4
|
+
autoload :API, "custodian/api"
|
5
|
+
autoload :CLI, "custodian/cli"
|
6
|
+
autoload :OS, "custodian/os"
|
5
7
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Custodian
|
4
|
+
|
5
|
+
# The API class is a Rack-compatible* application that encapsulates
|
6
|
+
# Custodian's interface.
|
7
|
+
#
|
8
|
+
# * http://rack.rubyforge.org/doc/files/SPEC.html.
|
9
|
+
class API
|
10
|
+
|
11
|
+
# Render the JSON representation of each sample.
|
12
|
+
#
|
13
|
+
# env - A Hash of CGI-like headers describing the
|
14
|
+
# environment under which the request was received.
|
15
|
+
#
|
16
|
+
# Returns an Array describing the HTTP response.
|
17
|
+
def call(env)
|
18
|
+
status = 200
|
19
|
+
|
20
|
+
headers = {
|
21
|
+
"Content-Type" => "application/json"
|
22
|
+
}
|
23
|
+
|
24
|
+
body = Custodian::Samplers.list.collect do |sampler|
|
25
|
+
{ description: sampler.description, sample: sampler.sample } if sampler.compatible?
|
26
|
+
end.compact.to_json
|
27
|
+
|
28
|
+
[status, headers, [body]]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require "thin"
|
2
|
+
require "rack"
|
3
|
+
require "rack/auth/basic"
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
module Custodian
|
7
|
+
|
8
|
+
# The CLI class encapsulates Custodian's command-line interface.
|
9
|
+
class CLI
|
10
|
+
DEFAULT_PORT = 5100
|
11
|
+
|
12
|
+
# Create a new command-line interface.
|
13
|
+
#
|
14
|
+
# arguments - An Array of String objects describing arguments
|
15
|
+
# passed to the program (see CLI#parse).
|
16
|
+
def initialize(arguments)
|
17
|
+
options = parse arguments
|
18
|
+
|
19
|
+
if samplers = options[:samplers]
|
20
|
+
load_samplers samplers
|
21
|
+
end
|
22
|
+
|
23
|
+
if options.include? :daemonize
|
24
|
+
deamonize
|
25
|
+
end
|
26
|
+
|
27
|
+
if pidfile = options[:pidfile]
|
28
|
+
write_pidfile pidfile
|
29
|
+
end
|
30
|
+
|
31
|
+
log "Custodian is accepting connections on port #{options[:port]}"
|
32
|
+
|
33
|
+
compatible_samplers = Custodian::Samplers.compatible
|
34
|
+
incompatible_samplers = Custodian::Samplers.incompatible
|
35
|
+
|
36
|
+
log "#{compatible_samplers.count} compatible samplers."
|
37
|
+
compatible_samplers.each do |s|
|
38
|
+
log " - #{s.description}"
|
39
|
+
end
|
40
|
+
|
41
|
+
log "#{incompatible_samplers.count} incompatible samplers."
|
42
|
+
incompatible_samplers.each do |s|
|
43
|
+
log " - #{s.description}"
|
44
|
+
end
|
45
|
+
|
46
|
+
log "CTRL+C to stop"
|
47
|
+
|
48
|
+
Thin::Logging.silent = true
|
49
|
+
Thin::Server.start '0.0.0.0', options[:port] do
|
50
|
+
use Rack::CommonLogger
|
51
|
+
use Rack::ShowExceptions
|
52
|
+
use Rack::Auth::Basic do |username, password|
|
53
|
+
[username, password] == [options[:username], options[:password]]
|
54
|
+
end if options.include? :username
|
55
|
+
|
56
|
+
run Custodian::API.new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Load samplers from the given directories.
|
63
|
+
#
|
64
|
+
# directories - An Array of Strings describing paths to directories
|
65
|
+
# that contain samplers.
|
66
|
+
#
|
67
|
+
# Returns nothing.
|
68
|
+
def load_samplers(directories)
|
69
|
+
directories.each do |directory|
|
70
|
+
Custodian::Samplers.load directory
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Write the process id to the given file.
|
75
|
+
#
|
76
|
+
# file - A String describing a path to a file. If the file does not exist, it
|
77
|
+
# will be created. If the file already exists, it will be overwritten.
|
78
|
+
#
|
79
|
+
# Returns nothing.
|
80
|
+
def write_pidfile(file)
|
81
|
+
File.open file, "w" do |file|
|
82
|
+
file << Process.id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Daemonize the process.
|
87
|
+
def deamonize
|
88
|
+
Process.daemon
|
89
|
+
end
|
90
|
+
|
91
|
+
# Parse the given arguments or exit with an error if they are malformed and/or invalid.
|
92
|
+
#
|
93
|
+
# arguments - An Array of String objects describing arguments passed to the program.
|
94
|
+
#
|
95
|
+
# Returns a Hash of options derived from the given arguments:
|
96
|
+
# :port - An Integer describing the port to listen on.
|
97
|
+
# :samplers - An Array of String objects describing paths to load samplers from.
|
98
|
+
# :daemonize - A Boolean describing whether or not to daemonize the process.
|
99
|
+
# :pidfile - A String describing a path to a file.
|
100
|
+
# :username - A String describing a username required by HTTP Basic authentication.
|
101
|
+
# :password - A String describing a password required by HTTP Basic authentication.
|
102
|
+
def parse(arguments)
|
103
|
+
options = {
|
104
|
+
port: DEFAULT_PORT
|
105
|
+
}
|
106
|
+
|
107
|
+
OptionParser.new do |o|
|
108
|
+
o.banner = "Usage: custodian [options]"
|
109
|
+
o.version = Custodian::VERSION
|
110
|
+
|
111
|
+
o.on "-p PORT", "--port PORT", "Listen on PORT (default #{options[:port]})" do |port|
|
112
|
+
options[:port] = port
|
113
|
+
end
|
114
|
+
|
115
|
+
o.on "-s DIR", "--samplers DIR", "Load samplers from DIR" do |samplers|
|
116
|
+
options[:samplers] = samplers.split(":")
|
117
|
+
end
|
118
|
+
|
119
|
+
o.on "--daemonize", "Daemonize the process" do
|
120
|
+
options[:daemonize] = true
|
121
|
+
end
|
122
|
+
|
123
|
+
o.on "--pidfile FILE", "Write the process id to FILE" do |pidfile|
|
124
|
+
options[:pidfile] = pidfile
|
125
|
+
end
|
126
|
+
|
127
|
+
o.on "--authenticate USERNAME:PASSWORD", "Authenticate clients with the given USERNAME and PASSWORD" do |credentials|
|
128
|
+
unless credentials.include? ":"
|
129
|
+
raise OptionParser::InvalidArgument, "must be a colon-separated string of username and password"
|
130
|
+
end
|
131
|
+
|
132
|
+
options[:username], options[:password] = credentials.split(":")
|
133
|
+
end
|
134
|
+
end.parse! arguments
|
135
|
+
|
136
|
+
options
|
137
|
+
rescue OptionParser::ParseError => e
|
138
|
+
error e
|
139
|
+
end
|
140
|
+
|
141
|
+
# Log a message to STDOUT.
|
142
|
+
#
|
143
|
+
# message - A String describing a message.
|
144
|
+
def log(message)
|
145
|
+
puts ">> #{message}"
|
146
|
+
end
|
147
|
+
|
148
|
+
# Print a message and exit with the given code.
|
149
|
+
#
|
150
|
+
# message - A String describing the message to print.
|
151
|
+
# code - An Integer describing the exit code (defaults to 1).
|
152
|
+
def error(message, code = 1)
|
153
|
+
puts "custodian: #{message}"
|
154
|
+
exit code
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
data/lib/custodian/os.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Custodian
|
2
|
+
|
3
|
+
# The OS module encapsulates Custodian's operating
|
4
|
+
# system detection algorithms.
|
5
|
+
module OS
|
6
|
+
|
7
|
+
# Determine whether the operating system is darwin.
|
8
|
+
#
|
9
|
+
# Returns a boolean describing whether the operating system
|
10
|
+
# is darwin.
|
11
|
+
def self.darwin?
|
12
|
+
RUBY_PLATFORM.downcase.include? "darwin"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Determine whether the operating system is linux.
|
16
|
+
#
|
17
|
+
# Returns a boolean describing whether the operating system
|
18
|
+
# is linux.
|
19
|
+
def self.linux?
|
20
|
+
RUBY_PLATFORM.downcase.include? "linux"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Custodian
|
4
|
+
|
5
|
+
# The Samplers module encapsulates a collection of samplers
|
6
|
+
# for various metrics.
|
7
|
+
module Samplers
|
8
|
+
autoload :Sampler, "custodian/samplers/sampler"
|
9
|
+
autoload :Utilities, "custodian/samplers/utilities"
|
10
|
+
|
11
|
+
@samplers = []
|
12
|
+
|
13
|
+
# List all samplers.
|
14
|
+
#
|
15
|
+
# Returns an Array of Sampler objects.
|
16
|
+
def self.list
|
17
|
+
@samplers
|
18
|
+
end
|
19
|
+
|
20
|
+
# List compatible samplers.
|
21
|
+
#
|
22
|
+
# Returns an Array of Sampler objects that are compatible
|
23
|
+
# with this system.
|
24
|
+
def self.compatible
|
25
|
+
@samplers.select { |s| s.compatible? }
|
26
|
+
end
|
27
|
+
|
28
|
+
# List incompatible samplers.
|
29
|
+
#
|
30
|
+
# Returns an Array of Sampler objects that are incompatible
|
31
|
+
# with this system.
|
32
|
+
def self.incompatible
|
33
|
+
@samplers.reject { |s| s.compatible? }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Register the given sampler.
|
37
|
+
#
|
38
|
+
# sampler - An object that inherits from Sampler.
|
39
|
+
#
|
40
|
+
# Returns nothing.
|
41
|
+
def self.register(sampler)
|
42
|
+
@samplers << sampler
|
43
|
+
end
|
44
|
+
|
45
|
+
# Remove the given sampler.
|
46
|
+
#
|
47
|
+
# sampler - An object that inherits from Sampler.
|
48
|
+
def self.remove(sampler)
|
49
|
+
@samplers.delete(sampler)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Replace one sampler with another.
|
53
|
+
#
|
54
|
+
# a - An object that inherits from Sampler.
|
55
|
+
# b - Another object that inherits from Sampler.
|
56
|
+
#
|
57
|
+
# Returns nothing.
|
58
|
+
def self.replace(a, b)
|
59
|
+
remove a
|
60
|
+
register b
|
61
|
+
end
|
62
|
+
|
63
|
+
# Clear all samplers.
|
64
|
+
def self.clear
|
65
|
+
@samplers = []
|
66
|
+
end
|
67
|
+
|
68
|
+
# Restore stock samplers.
|
69
|
+
def self.stock
|
70
|
+
load File.dirname(__FILE__) + "/samplers/darwin" if OS.darwin?
|
71
|
+
load File.dirname(__FILE__) + "/samplers/linux" if OS.linux?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Load samplers from the given directory.
|
75
|
+
#
|
76
|
+
# directory - A String describing a path to a directory. If the path is relative,
|
77
|
+
# it will be expanded from the current working directory. If the path
|
78
|
+
# is prefixed with a tilde, it will be expanded from the current user's
|
79
|
+
# home directory. If the path is prefixed with a tilde immediately followed
|
80
|
+
# by a username, it will be expanded from that user's home directory.
|
81
|
+
#
|
82
|
+
# Returns nothing.
|
83
|
+
def self.load(directory)
|
84
|
+
path = Pathname.new directory
|
85
|
+
|
86
|
+
# Allow referencing directories by relative and home path.
|
87
|
+
path = path.expand_path unless path.absolute?
|
88
|
+
|
89
|
+
unless path.directory?
|
90
|
+
raise StandardError, "#{directory} is not a directory"
|
91
|
+
end
|
92
|
+
|
93
|
+
path.each_child do |child|
|
94
|
+
require child if child.file?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
stock
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class CPU < Custodian::Samplers::Sampler
|
5
|
+
describe "CPU usage"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
cpu = `top -l 1`.match /CPU usage: ([0-9]+\.[0-9]+%) user, ([0-9]+\.[0-9]+%) sys, ([0-9]+\.[0-9]+%) idle/
|
9
|
+
|
10
|
+
{
|
11
|
+
"User" => cpu[1],
|
12
|
+
"System" => cpu[2],
|
13
|
+
"Idle" => cpu[3]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class Load < Custodian::Samplers::Sampler
|
5
|
+
describe "Load averages"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
uptime = `uptime`[/load averages: (.*)/, 1].split
|
9
|
+
|
10
|
+
{
|
11
|
+
"Last 5 minutes" => uptime[0],
|
12
|
+
"Last 10 minutes" => uptime[1],
|
13
|
+
"Last 15 minutes" => uptime[2]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class RAM < Custodian::Samplers::Sampler
|
5
|
+
describe "RAM usage"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
vmstat = `vm_stat`
|
9
|
+
|
10
|
+
pages_free = vmstat[/Pages free: +([0-9]+)/, 1].to_i + vmstat[/Pages speculative: +([0-9]+)/, 1].to_i
|
11
|
+
pages_inactive = vmstat[/Pages inactive: +([0-9]+)/, 1].to_i
|
12
|
+
pages_active = vmstat[/Pages active: +([0-9]+)/, 1].to_i
|
13
|
+
pages_wired = vmstat[/Pages wired down: +([0-9]+)/, 1].to_i
|
14
|
+
page_ins = vmstat[/Pageins: +([0-9]+)/, 1].to_i
|
15
|
+
page_outs = vmstat[/Pageouts: +([0-9]+)/, 1].to_i
|
16
|
+
|
17
|
+
{
|
18
|
+
"Free" => "#{pages_free * 4096 / 1024 / 1024} MB",
|
19
|
+
"Inactive" => "#{pages_inactive * 4096 / 1024 / 1024} MB",
|
20
|
+
"Active" => "#{pages_active * 4096 / 1024 / 1024} MB",
|
21
|
+
"Wired" => "#{pages_wired * 4096 / 1024 / 1024} MB",
|
22
|
+
"Page ins" => "#{page_ins * 4096 / 1024 / 1024} MB",
|
23
|
+
"Page outs" => "#{page_outs * 4096 / 1024 / 1024} MB"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class CPU < Custodian::Samplers::Sampler
|
5
|
+
describe "CPU usage"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
top = `top -b -n1`.match /Cpu\(s\): +([0-9.]+)%us, +([0-9.]+)%sy, +([0-9.%]+)ni, +([0-9.]+)%id, +([0-9.]+)%wa, +([0-9.]+)%hi, +([0-9.]+)%si, +([0-9.]+)%st/
|
9
|
+
|
10
|
+
us = top[1].to_f # User CPU time: The time the CPU has spent running users' processes that are not niced.
|
11
|
+
sy = top[2].to_f # System CPU time: The time the CPU has spent running the kernel and its processes.
|
12
|
+
ni = top[3].to_f # Nice CPU time: The time the CPU has spent running users' proccess that have been niced.
|
13
|
+
id = top[4].to_f # Hardware IRQ: The amount of time the CPU has been servicing hardware interrupts.
|
14
|
+
wa = top[5].to_f # iowait: Amount of time the CPU has been waiting for I/O to complete.
|
15
|
+
hi = top[6].to_f # Hardware IRQ: The amount of time the CPU has been servicing hardware interrupts.
|
16
|
+
si = top[7].to_f # Hardware Interrupts: The amount of time the CPU has been servicing software interrupts.
|
17
|
+
st = top[8].to_f # Steal Time: The amount of CPU 'stolen' from this virtual machine by the hypervisor for other tasks.
|
18
|
+
|
19
|
+
{
|
20
|
+
"User" => "#{us + ni}%",
|
21
|
+
"System" => "#{sy + wa + hi + si + st}%",
|
22
|
+
"Idle" => "#{id}%"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class Load < Custodian::Samplers::Sampler
|
5
|
+
describe "Load averages"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
uptime = `uptime`.match /load average: ([0-9.]+), ([0-9.]+), ([0-9.]+)/
|
9
|
+
|
10
|
+
{
|
11
|
+
"Last 5 minutes" => uptime[1],
|
12
|
+
"Last 10 minutes" => uptime[2],
|
13
|
+
"Last 15 minutes" => uptime[3]
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
class RAM < Custodian::Samplers::Sampler
|
5
|
+
describe "RAM usage"
|
6
|
+
|
7
|
+
def self.sample
|
8
|
+
free = `free -m`.match /Mem: +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+) +([0-9]+)/
|
9
|
+
|
10
|
+
{
|
11
|
+
"Total" => "#{free[1]} MB",
|
12
|
+
"Used" => "#{free[2]} MB",
|
13
|
+
"Free" => "#{free[3]} MB",
|
14
|
+
"Buffers" => "#{free[4]} MB",
|
15
|
+
"Cached" => "#{free[5]} MB"
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
# Base class for samplers.
|
5
|
+
class Sampler
|
6
|
+
include Custodian::Samplers::Utilities
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Describe the sampler.
|
11
|
+
#
|
12
|
+
# description - A String describing the sampler.
|
13
|
+
#
|
14
|
+
# Returns nothing.
|
15
|
+
def describe(description)
|
16
|
+
@description = description
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the sampler's description.
|
20
|
+
def description
|
21
|
+
@description
|
22
|
+
end
|
23
|
+
|
24
|
+
# Make a sample from this sampler.
|
25
|
+
#
|
26
|
+
# Examples
|
27
|
+
#
|
28
|
+
# CPU.sample # => { "User" => "10%", "System" => "5%", "Idle" => "85%" }
|
29
|
+
#
|
30
|
+
# Returns an Integer, Symbol, String, Hash or Array (or any combination thereof)
|
31
|
+
# describing the outcome of the sample.
|
32
|
+
def sample
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determine whether the sampler is compatible with this system.
|
37
|
+
#
|
38
|
+
# Returns true if it is compatible, or false if it isn't.
|
39
|
+
def compatible?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Register samplers for classes that inherit from this class.
|
48
|
+
#
|
49
|
+
# sampler - The Class that inherited this class.
|
50
|
+
#
|
51
|
+
# Returns nothing.
|
52
|
+
def self.inherited(sampler)
|
53
|
+
Custodian::Samplers.register(sampler)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Custodian
|
2
|
+
module Samplers
|
3
|
+
|
4
|
+
# Utility methods for samplers.
|
5
|
+
module Utilities
|
6
|
+
|
7
|
+
# Determine whether the given program exists.
|
8
|
+
#
|
9
|
+
# program - A String describing the name of a program.
|
10
|
+
#
|
11
|
+
# Returns true if the program exists, or false if it doesn't.
|
12
|
+
def self.program_exists?(program)
|
13
|
+
system "which #{command} > /dev/null 2>&1"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Determine whether the given directory exists.
|
17
|
+
#
|
18
|
+
# directory - A String describing the path to a directory.
|
19
|
+
#
|
20
|
+
# Returns true if the directory exists, or false if it doesn't.
|
21
|
+
def self.directory_exists?(directory)
|
22
|
+
Pathname.new(directory).directory?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Determine whether the given file exists.
|
26
|
+
#
|
27
|
+
# file - A String describing the path to a file.
|
28
|
+
#
|
29
|
+
# Returns true if the file exists, or false if it doesn't.
|
30
|
+
def self.file_exists?(file)
|
31
|
+
Pathname.new(file).file?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/custodian/version.rb
CHANGED
data/test/cli_test.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "custodian"
|
2
|
+
require "minitest/spec"
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "rack/test"
|
5
|
+
|
6
|
+
describe Custodian::API do
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app
|
10
|
+
Custodian::API.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "responds with HTTP 200 OK" do
|
14
|
+
get "/"
|
15
|
+
|
16
|
+
last_response.ok?.must_equal true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "responds with valid JSON" do
|
20
|
+
get "/"
|
21
|
+
|
22
|
+
JSON.parse(last_response.body).must_be_instance_of Array
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "custodian"
|
2
|
+
require "minitest/spec"
|
3
|
+
require "minitest/autorun"
|
4
|
+
|
5
|
+
describe Custodian::Samplers::Sampler do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@sampler = Custodian::Samplers::Sampler
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can sample" do
|
12
|
+
@sampler.sample
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can be described" do
|
16
|
+
@sampler.description.must_equal nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can determine whether its compatible" do
|
20
|
+
@sampler.compatible?.must_equal true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "custodian"
|
2
|
+
require "minitest/spec"
|
3
|
+
require "minitest/autorun"
|
4
|
+
|
5
|
+
describe Custodian::Samplers do
|
6
|
+
|
7
|
+
before do
|
8
|
+
Custodian::Samplers.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
Custodian::Samplers.stock
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can list samplers" do
|
16
|
+
Custodian::Samplers.list.must_be_instance_of Array
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can list compatible samplers" do
|
20
|
+
Custodian::Samplers.compatible.must_be_instance_of Array
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can list incompatible samplers" do
|
24
|
+
Custodian::Samplers.incompatible.must_be_instance_of Array
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can register new samplers" do
|
28
|
+
Custodian::Samplers::register Custodian::Samplers::CPU
|
29
|
+
|
30
|
+
Custodian::Samplers.list.must_include Custodian::Samplers::CPU
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can remove samplers" do
|
34
|
+
Custodian::Samplers::remove Custodian::Samplers::CPU
|
35
|
+
|
36
|
+
Custodian::Samplers.list.wont_include Custodian::Samplers::CPU
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can replace samplers" do
|
40
|
+
Custodian::Samplers.register Custodian::Samplers::CPU
|
41
|
+
Custodian::Samplers.replace Custodian::Samplers::CPU, Custodian::Samplers::RAM
|
42
|
+
|
43
|
+
Custodian::Samplers.list.must_include Custodian::Samplers::RAM
|
44
|
+
Custodian::Samplers.list.wont_include Custodian::Samplers::CPU
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: custodian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,22 +9,86 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-02-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70234530717300 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70234530717300
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thin
|
27
|
+
requirement: &70234530713980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70234530713980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rack
|
38
|
+
requirement: &70234530708540 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70234530708540
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: active_support
|
49
|
+
requirement: &70234536390620 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70234536390620
|
14
58
|
description: Custodian is a lightweight resource monitor that is easy to use and augment
|
15
59
|
email:
|
16
60
|
- jgorset@gmail.com
|
17
|
-
executables:
|
61
|
+
executables:
|
62
|
+
- custodian
|
18
63
|
extensions: []
|
19
64
|
extra_rdoc_files: []
|
20
65
|
files:
|
21
66
|
- .gitignore
|
22
67
|
- Gemfile
|
68
|
+
- LICENSE
|
23
69
|
- README.md
|
24
70
|
- Rakefile
|
71
|
+
- bin/custodian
|
25
72
|
- custodian.gemspec
|
73
|
+
- docs/images/example.png
|
26
74
|
- lib/custodian.rb
|
75
|
+
- lib/custodian/api.rb
|
76
|
+
- lib/custodian/cli.rb
|
77
|
+
- lib/custodian/os.rb
|
78
|
+
- lib/custodian/samplers.rb
|
79
|
+
- lib/custodian/samplers/darwin/cpu.rb
|
80
|
+
- lib/custodian/samplers/darwin/load.rb
|
81
|
+
- lib/custodian/samplers/darwin/ram.rb
|
82
|
+
- lib/custodian/samplers/darwin/who.rb
|
83
|
+
- lib/custodian/samplers/linux/cpu.rb
|
84
|
+
- lib/custodian/samplers/linux/load.rb
|
85
|
+
- lib/custodian/samplers/linux/ram.rb
|
86
|
+
- lib/custodian/samplers/sampler.rb
|
87
|
+
- lib/custodian/samplers/utilities.rb
|
27
88
|
- lib/custodian/version.rb
|
89
|
+
- test/cli_test.rb
|
90
|
+
- test/sampler_test.rb
|
91
|
+
- test/samplers_test.rb
|
28
92
|
homepage: http://github.com/jgorset/custodian
|
29
93
|
licenses: []
|
30
94
|
post_install_message:
|
@@ -49,4 +113,7 @@ rubygems_version: 1.8.11
|
|
49
113
|
signing_key:
|
50
114
|
specification_version: 3
|
51
115
|
summary: Lightweight resource monitor
|
52
|
-
test_files:
|
116
|
+
test_files:
|
117
|
+
- test/cli_test.rb
|
118
|
+
- test/sampler_test.rb
|
119
|
+
- test/samplers_test.rb
|