slashport 0.15.10
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/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
|