slashport 0.15.10
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +35 -0
- data/app/controllers/application.rb +2 -0
- data/app/controllers/cfg.rb +8 -0
- data/app/controllers/exceptions.rb +13 -0
- data/app/controllers/var.rb +27 -0
- data/app/controllers/vardoc.rb +7 -0
- data/app/helpers/cfg_helper.rb +5 -0
- data/app/helpers/global_helpers.rb +5 -0
- data/app/helpers/var_helper.rb +5 -0
- data/app/helpers/vardoc_helper.rb +5 -0
- data/app/models/base/attribute.rb +13 -0
- data/app/models/base/component.rb +206 -0
- data/app/models/base/exec.rb +40 -0
- data/app/models/base/registry.rb +4 -0
- data/app/models/base/tuple.rb +31 -0
- data/app/models/components/linuxhost.rb +125 -0
- data/app/models/components/linuxprocess.rb +55 -0
- data/app/models/components/mysql.rb +110 -0
- data/app/models/components/puppet.rb +20 -0
- data/app/views/cfg/index.html.erb +1 -0
- data/app/views/exceptions/not_acceptable.html.erb +63 -0
- data/app/views/exceptions/not_found.html.erb +47 -0
- data/app/views/layout/application.html.erb +12 -0
- data/app/views/var/index.json.erb +1 -0
- data/app/views/var/index.pp.erb +10 -0
- data/app/views/var/index.text.erb +18 -0
- data/app/views/vardoc/index.html.erb +1 -0
- data/autotest/discover.rb +1 -0
- data/autotest/merb.rb +149 -0
- data/autotest/merb_rspec.rb +165 -0
- data/bin/slashport +130 -0
- data/bin/slashportfetch +103 -0
- data/config/environments/development.rb +15 -0
- data/config/environments/production.rb +10 -0
- data/config/environments/rake.rb +11 -0
- data/config/environments/staging.rb +10 -0
- data/config/environments/test.rb +12 -0
- data/config/init.rb +26 -0
- data/config/rack.rb +11 -0
- data/config/router.rb +41 -0
- data/config/test.conf +2 -0
- data/doc/rdoc/generators/merb_generator.rb +1362 -0
- data/doc/rdoc/generators/template/merb/api_grease.js +640 -0
- data/doc/rdoc/generators/template/merb/index.html.erb +37 -0
- data/doc/rdoc/generators/template/merb/merb.css +252 -0
- data/doc/rdoc/generators/template/merb/merb.rb +351 -0
- data/doc/rdoc/generators/template/merb/merb_doc_styles.css +492 -0
- data/doc/rdoc/generators/template/merb/prototype.js +2515 -0
- data/lib/slashport.rb +93 -0
- data/public/favicon.ico +0 -0
- data/public/images/merb.jpg +0 -0
- data/public/javascripts/application.js +1 -0
- data/public/merb.fcgi +22 -0
- data/public/robots.txt +5 -0
- data/public/stylesheets/master.css +119 -0
- data/spec/requests/cfg_spec.rb +7 -0
- data/spec/requests/config_spec.rb +7 -0
- data/spec/requests/configdoc_spec.rb +7 -0
- data/spec/requests/var_spec.rb +7 -0
- data/spec/requests/vardoc_spec.rb +7 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_helper.rb +20 -0
- metadata +156 -0
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
# Load the basic runtime dependencies; this will include
|
10
|
+
# any plugins and therefore plugin rake tasks.
|
11
|
+
init_env = ENV['MERB_ENV'] || 'rake'
|
12
|
+
Merb.load_dependencies(:environment => init_env)
|
13
|
+
|
14
|
+
# Get Merb plugins and dependencies
|
15
|
+
Merb::Plugins.rakefiles.each { |r| require r }
|
16
|
+
|
17
|
+
# Load any app level custom rakefile extensions from lib/tasks
|
18
|
+
tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks")
|
19
|
+
rake_files = Dir["#{tasks_path}/*.rake"]
|
20
|
+
rake_files.each{|rake_file| load rake_file }
|
21
|
+
|
22
|
+
desc "Start runner environment"
|
23
|
+
task :merb_env do
|
24
|
+
Merb.start_environment(:environment => init_env, :adapter => 'runner')
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
require 'merb-core/test/tasks/spectasks'
|
29
|
+
desc 'Default: run spec examples'
|
30
|
+
task :default => 'spec'
|
31
|
+
|
32
|
+
##############################################################################
|
33
|
+
# ADD YOUR CUSTOM TASKS IN /lib/tasks
|
34
|
+
# NAME YOUR RAKE FILES file_name.rake
|
35
|
+
##############################################################################
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Var < Application
|
2
|
+
def index
|
3
|
+
only_provides :json, :text, :pp
|
4
|
+
|
5
|
+
filter = Hash.new
|
6
|
+
params.each do |key, value|
|
7
|
+
# skip parameters from merb itself
|
8
|
+
next if ["action", "controller", "format", "id"].include?(key)
|
9
|
+
next if value == nil
|
10
|
+
|
11
|
+
# If it looks like a regex, treat it like one.
|
12
|
+
# That is, if the value is /something/ (begin and end with slash)
|
13
|
+
if value =~ /^\/.+\/$/
|
14
|
+
filter[key] = Regexp.new(value[1..-2]) rescue value
|
15
|
+
else
|
16
|
+
# otherwise, treat it like a literal string to full match.
|
17
|
+
filter[key] = Regexp.new("^#{Regexp.escape(value)}$")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# ensure filter isn't changed
|
22
|
+
filter.freeze
|
23
|
+
|
24
|
+
@attributes = SlashPort::Component.get_attributes(filter)
|
25
|
+
display @attributes
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SlashPort
|
2
|
+
class Attribute
|
3
|
+
attr_reader :handler
|
4
|
+
attr_reader :doc
|
5
|
+
attr_reader :sortkeys
|
6
|
+
|
7
|
+
def initialize(handler, doc, sortkeys=[])
|
8
|
+
@handler = handler
|
9
|
+
@doc = doc
|
10
|
+
@sortkeys = sortkeys
|
11
|
+
end
|
12
|
+
end # class Attribute
|
13
|
+
end # module SlashPort
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module SlashPort
|
4
|
+
class Component
|
5
|
+
@@subclasses = Array.new
|
6
|
+
@@components = Array.new
|
7
|
+
|
8
|
+
def attributes
|
9
|
+
return self.class.attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def configs
|
13
|
+
return self.class.configs
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_attributes(filter=nil)
|
17
|
+
get_things(attributes, filter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_configs(filter=nil)
|
21
|
+
get_things(configs, filter)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _want(pattern, *values)
|
25
|
+
if pattern == nil
|
26
|
+
return true
|
27
|
+
end
|
28
|
+
|
29
|
+
want = false
|
30
|
+
values.each do |value|
|
31
|
+
#puts "Checking #{pattern.class}/#{pattern} against #{value.class}/#{value.inspect}"
|
32
|
+
if (pattern.is_a?(Regexp) and !!(value.to_s =~ pattern))
|
33
|
+
#puts "Match! =~"
|
34
|
+
want = true
|
35
|
+
break
|
36
|
+
elsif value == pattern
|
37
|
+
#puts "Match! =="
|
38
|
+
want = true
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return want
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_things(thing, filter=nil)
|
46
|
+
return unless _want(filter["component"], self.class.label)
|
47
|
+
|
48
|
+
data = []
|
49
|
+
|
50
|
+
thing.each do |section, var|
|
51
|
+
next unless _want(filter["section"], section)
|
52
|
+
results = self.send(var.handler)
|
53
|
+
next if results == nil
|
54
|
+
results = [results] if !results.is_a?(Array)
|
55
|
+
|
56
|
+
results.each do |result|
|
57
|
+
result.labels["component"] = self.class.label
|
58
|
+
result.labels["section"] = section
|
59
|
+
result.labels["host"] = Socket.gethostname
|
60
|
+
|
61
|
+
keep = true
|
62
|
+
filter.each do |filterkey,filtervalue|
|
63
|
+
want = _want(filtervalue, result.labels[filterkey],
|
64
|
+
result.data[filterkey])
|
65
|
+
#puts "Filter: #{want} => #{filterkey} #{filtervalue}"
|
66
|
+
|
67
|
+
if (!want)
|
68
|
+
keep = false
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if keep
|
74
|
+
# if our filter includes a 'data' name, then we
|
75
|
+
# should strip all nonmatching data entries.
|
76
|
+
if (result.data.keys & filter.keys).length > 0
|
77
|
+
result.data.each_key do |key|
|
78
|
+
next if filter.has_key?(key)
|
79
|
+
#puts "Removing #{key} data"
|
80
|
+
result.data.delete(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
data << result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
return data
|
88
|
+
end
|
89
|
+
|
90
|
+
def path(*names)
|
91
|
+
return [self.class.label, *names].join("/")
|
92
|
+
end
|
93
|
+
|
94
|
+
# See Class#inherited for what this method
|
95
|
+
def self.inherited(subclass)
|
96
|
+
puts "#{subclass.name} inherits #{self.name}"
|
97
|
+
@@subclasses << subclass
|
98
|
+
|
99
|
+
if subclass.respond_to?(:class_initialize)
|
100
|
+
subclass.class_initialize
|
101
|
+
end
|
102
|
+
end # def self.inherited
|
103
|
+
|
104
|
+
# class-level to easily map a attribute name to a method
|
105
|
+
# arguments:
|
106
|
+
# :name => attribute name
|
107
|
+
# :handler => method handler name
|
108
|
+
# :doc => attribute documentation
|
109
|
+
# :sort => [optional] array of keys for sort (used with var.text output)
|
110
|
+
def self.attribute(options = {})
|
111
|
+
if options[:doc] == nil
|
112
|
+
raise "Attribute #{self.name}/#{name} has no description"
|
113
|
+
end
|
114
|
+
name = options[:name]
|
115
|
+
puts "#{self.name}: new attribute #{name}"
|
116
|
+
options[:sort] ||= []
|
117
|
+
|
118
|
+
# remember: this is a class-level instance attribute
|
119
|
+
@attributes[options[:name]] = Attribute.new(options[:handler], options[:doc], options[:sort])
|
120
|
+
end # def self.attribute
|
121
|
+
|
122
|
+
# class-level to easily map a variable name to a handler
|
123
|
+
def self.config(name, handler, description=nil)
|
124
|
+
if description == nil
|
125
|
+
raise "Config #{self.name}/#{name} has no description"
|
126
|
+
end
|
127
|
+
#puts "#{self.name}: new config #{name}"
|
128
|
+
|
129
|
+
# remember: this is a class-level instance variable
|
130
|
+
@configs[name] = Variable.new(handler, description)
|
131
|
+
end # def self.config
|
132
|
+
|
133
|
+
def self.configs(filter=nil)
|
134
|
+
return @configs
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.attributes(filter=nil)
|
138
|
+
return @attributes
|
139
|
+
end
|
140
|
+
|
141
|
+
# class-level initialization. This is called when ruby first
|
142
|
+
# creates this class object, a hack made possible by
|
143
|
+
# overriding Class#inherited (see 'def inherited' above).
|
144
|
+
def self.class_initialize
|
145
|
+
#puts "#{self}::class_initialize"
|
146
|
+
# remember, this is a class-level instance attribute
|
147
|
+
@attributes = Registry.new
|
148
|
+
@configs = Registry.new
|
149
|
+
@label = self.name.split("::")[-1].downcase
|
150
|
+
|
151
|
+
# disable this component by default
|
152
|
+
#puts "Disabling component '#{@label}' (default action, you must enable it if you want to use it)"
|
153
|
+
#disable
|
154
|
+
enable
|
155
|
+
end # def.class_initialize
|
156
|
+
|
157
|
+
# Show me all subclasses of SlashPort::Component
|
158
|
+
def self.components
|
159
|
+
if @@components.length == 0
|
160
|
+
@@subclasses.each do |klass|
|
161
|
+
component = klass.new
|
162
|
+
@@components << component
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
return @@components
|
167
|
+
end # def self.components
|
168
|
+
|
169
|
+
def self.get_things(thing, filter=nil)
|
170
|
+
data = []
|
171
|
+
self.components.each do |component|
|
172
|
+
next unless component.class.is_enabled?
|
173
|
+
result = component.send("get_#{thing}", filter)
|
174
|
+
if result
|
175
|
+
data += result
|
176
|
+
end
|
177
|
+
end
|
178
|
+
return data
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.get_attributes(filter=nil)
|
182
|
+
return self.get_things("attributes", filter)
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.get_configs(filter=nil)
|
186
|
+
return self.get_things("configs", filter)
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.label
|
190
|
+
return @label
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.disable
|
194
|
+
@active = false
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.enable
|
198
|
+
@active = true
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.is_enabled?
|
202
|
+
return @active
|
203
|
+
end
|
204
|
+
|
205
|
+
end # class Component
|
206
|
+
end # module SlashPort
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class SlashPort::Exec
|
2
|
+
def initialize(cmd)
|
3
|
+
@cmd = cmd
|
4
|
+
end
|
5
|
+
|
6
|
+
def run
|
7
|
+
output = `#{@cmd}`
|
8
|
+
code = $?.exitstatus
|
9
|
+
|
10
|
+
return [output, code]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_tuple
|
14
|
+
data = []
|
15
|
+
|
16
|
+
output, code = run
|
17
|
+
lines = output.split(/\r?\n/)
|
18
|
+
|
19
|
+
if lines.length == 0
|
20
|
+
tuple = SlashPort::Tuple.new
|
21
|
+
tuple.data["output-lines"] = lines.length
|
22
|
+
tuple.data["exit-code"] = code
|
23
|
+
data << tuple
|
24
|
+
end
|
25
|
+
|
26
|
+
lines.each do |line|
|
27
|
+
tuple = SlashPort::Tuple.new
|
28
|
+
tuple.data["exit-code"] = code
|
29
|
+
begin
|
30
|
+
tuple.data["value"] = Float(line)
|
31
|
+
rescue ArgumentError => e
|
32
|
+
tuple.labels["string"] = 1
|
33
|
+
tuple.data["value"] = line
|
34
|
+
end
|
35
|
+
|
36
|
+
data << tuple
|
37
|
+
end
|
38
|
+
return data
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
# A tuple is like a slashport row.
|
3
|
+
# Each tuple contains a set of labels and data.
|
4
|
+
# Each label or data is a key:value pair, allowing you
|
5
|
+
# to name each label and each data.
|
6
|
+
#
|
7
|
+
# For example, transmit packet counts for a network interface would have
|
8
|
+
# a label of 'interface=eth0', for example, and a data of 'txpackets=12345'
|
9
|
+
# Multiple labels are supported/encouraged, as are multiple data.
|
10
|
+
# Following the interface example, you could hold all metrics for a single
|
11
|
+
# network interface with a single Tuple: ie;
|
12
|
+
# labels: { "interface" => "eth0" }
|
13
|
+
# data: { "txpackets" => 298374, "rxpackets" => 7577, "speed" => 1000 }
|
14
|
+
#
|
15
|
+
# Labels are intended to represent attributes that will not change.
|
16
|
+
# Data are intended to represent attributes that can change.
|
17
|
+
class SlashPort::Tuple
|
18
|
+
attr_accessor :labels
|
19
|
+
attr_accessor :data
|
20
|
+
def initialize
|
21
|
+
@labels = Hash.new
|
22
|
+
@data = Hash.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
return {
|
27
|
+
"labels" => @labels,
|
28
|
+
"data" => @data,
|
29
|
+
}.to_json
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
class SlashPort::Component
|
4
|
+
class LinuxHost < SlashPort::Component
|
5
|
+
attribute :name => "uptime",
|
6
|
+
:handler => :Uptime,
|
7
|
+
:doc => "Uptime in seconds"
|
8
|
+
|
9
|
+
attribute :name => "interfaces",
|
10
|
+
:handler => :IfStats,
|
11
|
+
:sort => ["interface", "field"],
|
12
|
+
:doc => "Interface Statistics"
|
13
|
+
|
14
|
+
attribute :name => "memory",
|
15
|
+
:handler => :MemStats,
|
16
|
+
:doc => "Memory stats from /proc/meminfo"
|
17
|
+
|
18
|
+
attribute :name => "disk",
|
19
|
+
:handler => :DiskStats,
|
20
|
+
:doc => "Disk stats from 'df'"
|
21
|
+
|
22
|
+
attribute :name => "load",
|
23
|
+
:handler => :LoadAverage,
|
24
|
+
:doc => "System load average reported by uptime(1)"
|
25
|
+
|
26
|
+
def _reapchild
|
27
|
+
begin
|
28
|
+
Process.wait(-1, Process::WNOHANG)
|
29
|
+
rescue Errno::ECHILD
|
30
|
+
# ignore
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def Uptime
|
35
|
+
tuple = SlashPort::Tuple.new
|
36
|
+
tuple.data["uptime"] = File.open("/proc/uptime").read().split(" ")[0]
|
37
|
+
return tuple
|
38
|
+
end
|
39
|
+
|
40
|
+
def IfStats
|
41
|
+
data = Array.new
|
42
|
+
File.open("/proc/net/dev").readlines().each do |line|
|
43
|
+
line.chomp!
|
44
|
+
next if line =~ /^Inter-|^ face/
|
45
|
+
|
46
|
+
tuple = SlashPort::Tuple.new
|
47
|
+
|
48
|
+
fields = %w[rx_bytes rx_packets rx_errors rx_drop rx_fifo rx_frame
|
49
|
+
rx_compressed rx_multicast tx_bytes tx_packets tx_errors
|
50
|
+
tx_drop tx_fifo tx_colls tx_carrier tx_compressed]
|
51
|
+
|
52
|
+
interface, values = line.split(":")
|
53
|
+
interface.gsub!(/\s+/, "")
|
54
|
+
|
55
|
+
tuple.labels["interface"] = interface
|
56
|
+
fields.zip(values.split).each do |field,value|
|
57
|
+
tuple.data[field] = value
|
58
|
+
end
|
59
|
+
data << tuple
|
60
|
+
end
|
61
|
+
return data
|
62
|
+
end
|
63
|
+
|
64
|
+
def MemStats
|
65
|
+
data = Array.new
|
66
|
+
tuple = SlashPort::Tuple.new
|
67
|
+
File.open("/proc/meminfo").readlines().each do |line|
|
68
|
+
line.chomp!
|
69
|
+
key, value, unit = line.split(/[: ]+/)
|
70
|
+
value = value.to_i
|
71
|
+
if unit == "kB"
|
72
|
+
value *= 1024
|
73
|
+
end
|
74
|
+
|
75
|
+
tuple.data[key.downcase] = value
|
76
|
+
end
|
77
|
+
data << tuple
|
78
|
+
return data
|
79
|
+
end # def MemStats
|
80
|
+
|
81
|
+
def DiskStats
|
82
|
+
data = Array.new
|
83
|
+
IO.popen("df -PlB 1").readlines().each do |line|
|
84
|
+
# skip header
|
85
|
+
next if line =~ /Filesystem\s/
|
86
|
+
# skip nonpath sources (tmpfs, udev, etc)
|
87
|
+
next unless line =~ /^\//
|
88
|
+
|
89
|
+
line.chomp!
|
90
|
+
tuple = SlashPort::Tuple.new
|
91
|
+
fields = %w[size used available percentused]
|
92
|
+
values = line.split
|
93
|
+
source = values.shift
|
94
|
+
mount = values.pop
|
95
|
+
|
96
|
+
tuple.labels["source"] = source
|
97
|
+
tuple.labels["mount"] = mount
|
98
|
+
fields.zip(values).each do |field,value|
|
99
|
+
if field == "percentused"
|
100
|
+
value = value.to_i / 100.0
|
101
|
+
end
|
102
|
+
|
103
|
+
tuple.data[field] = value
|
104
|
+
end
|
105
|
+
data << tuple
|
106
|
+
end
|
107
|
+
_reapchild
|
108
|
+
return data
|
109
|
+
end # def DiskStats
|
110
|
+
|
111
|
+
def LoadAverage
|
112
|
+
data = Array.new
|
113
|
+
tuple = SlashPort::Tuple.new
|
114
|
+
loads = %x{uptime}.chomp.delete(",").split(/ +/)[-3..-1].map { |x| x.to_f }
|
115
|
+
_reapchild
|
116
|
+
load1, load5, load15 = loads
|
117
|
+
tuple.data["load-1min"] = load1
|
118
|
+
tuple.data["load-5min"] = load5
|
119
|
+
tuple.data["load-15min"] = load15
|
120
|
+
|
121
|
+
data << tuple
|
122
|
+
return data
|
123
|
+
end # def LoadAverage
|
124
|
+
end # class LinuxHost
|
125
|
+
end # class SlashPort::Component
|