silw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ module Silw
2
+ class Command
3
+ def initialize(auth_options={})
4
+ @username = auth_options[:authentication][:username]
5
+ @password = auth_options[:authentication][:password]
6
+ pub_key = File.expand_path(@password)
7
+ if File.exists?(pub_key)
8
+ @pub_key = pub_key
9
+ end
10
+ raise RuntimeError unless @password or @pub_key
11
+ end
12
+
13
+ # see: http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_3.html
14
+ def run(command, remotes={})
15
+ plugin = Kernel.const_get("Silw::Plugins::#{command.capitalize}").new
16
+ plugin.instance_variable_set(:@username, @username)
17
+ if @pub_key
18
+ plugin.instance_variable_set(:@pub_key, @pub_key)
19
+ else
20
+ plugin.instance_variable_set(:@password, @password)
21
+ end
22
+ plugin.run(remotes)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ module Silw
2
+ module Helpers
3
+ def h(*args)
4
+ escape_html(*args)
5
+ end
6
+
7
+ def find_template(views, name, engine, &block)
8
+ Array(views).each { |v| super(v, name, engine, &block) }
9
+ end
10
+
11
+ def base_url
12
+ "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/"
13
+ end
14
+
15
+ def partial(thing, locals = {})
16
+ name = case thing
17
+ when String then thing
18
+ else thing.class.to_s.demodulize.underscore
19
+ end
20
+ haml :"partials/_#{name}", :locals => { name.to_sym => thing }.merge(locals)
21
+ end
22
+
23
+ def url_for(thing, options = {})
24
+ url = thing.respond_to?(:to_url) ? thing.to_url : thing.to_s
25
+ url = "#{base_url.sub(/\/$/, '')}#{url}" if options[:absolute]
26
+ url
27
+ end
28
+
29
+ def production?
30
+ settings.environment.to_sym == :production
31
+ end
32
+
33
+ def user_logged_in?
34
+ session[:auth].present?
35
+ end
36
+
37
+ def link_to(title, target = "", options = {})
38
+ options[:href] = target.respond_to?(:to_url) ? target.to_url : target
39
+ options[:data] ||= {}
40
+ [:method, :confirm].each { |a| options[:data][a] = options.delete(a) }
41
+ haml "%a#{options} #{title}"
42
+ end
43
+
44
+ def like_filesize(nr)
45
+ {
46
+ 'B' => 1024,
47
+ 'KB' => 1024 * 1024,
48
+ 'MB' => 1024 * 1024 * 1024,
49
+ 'GB' => 1024 * 1024 * 1024 * 1024,
50
+ 'TB' => 1024 * 1024 * 1024 * 1024 * 1024
51
+ }.each_pair{|e, s| return "#{(nr.to_f / (s / 1024)).round(2)}#{e}" if nr < s }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ require "silw/command"
2
+
3
+ module Silw
4
+ class Plugin < Command
5
+ attr :username, :password, :pub_key
6
+
7
+ def run
8
+ puts "implement me"
9
+ end
10
+
11
+ def self.inherited(base)
12
+ subclasses << base
13
+ end
14
+
15
+ def self.subclasses
16
+ @subclasses ||= []
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,55 @@
1
+ require 'net/sftp'
2
+
3
+ # CPU info - report the total CPU usage for a remote system.
4
+ # For more details about 'proc/stat' see:
5
+ # - http://www.linuxhowtos.org/System/procstat.htm
6
+ #
7
+ # @return - a JSON containing the total CPU usage as a percentage
8
+ module Silw
9
+ module Plugins
10
+ class Cpu
11
+ attr_accessor :cpu_stats
12
+ CpuStats = Struct.new :user, :nice, :system, :idle, :iowait,
13
+ :irq, :softirq, :steal, :guest, :guest_nice,
14
+ :total
15
+
16
+ def run(args)
17
+ host = args[:at]
18
+
19
+ if fixture_cpu_at_t0 = args[:fixture_cpu_at_t0] and fixture_cpu_at_t1 = args[:fixture_cpu_at_t1]
20
+ cpu_stats = parse_stats File.read(fixture_cpu_at_t0), File.read(fixture_cpu_at_t1)
21
+ else
22
+ cpu_at_t0 = get_stat(host)
23
+ sleep 0.5
24
+ cpu_at_t1 = get_stat(host)
25
+ cpu_stats = parse_stats cpu_at_t0, cpu_at_t1
26
+ end
27
+
28
+ {:host => host, :cpu => cpu_stats}
29
+ end
30
+
31
+ private #methods
32
+ def proc_stat_struct(cpu_times)
33
+ parts = cpu_times.split(/\n/).first.split
34
+ times = parts[1..-1].map(&:to_i)
35
+ CpuStats[*times].tap {|r| r[:total] = times.reduce(:+)}
36
+ end
37
+
38
+ def parse_stats(cpu_at_t0, cpu_at_t1)
39
+ cpu0 = proc_stat_struct cpu_at_t0
40
+ cpu1 = proc_stat_struct cpu_at_t1
41
+ idle = cpu1.idle - cpu0.idle
42
+ total = cpu1.total - cpu0.total
43
+ usage = total - idle
44
+ # {:cpu_usage => ("%.1f%%" % (100.0 * usage / total))}
45
+ {:usage => (100.0 * usage / total).round}
46
+ end
47
+
48
+ def get_stat(remote, opts={})
49
+ Net::SFTP.start(remote, @username, :keys => @pub_key ) do |scp|
50
+ return scp.download!('/proc/stat').split('\n').first
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ require 'net/sftp'
2
+ require 'json'
3
+
4
+ module Silw
5
+ module Plugins
6
+
7
+ # verify the disk usage on a remote host using the contents of
8
+ # /proc/diskstats file.
9
+ # See: https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
10
+ #
11
+ # return a JSON containing the disk [read, write] stats
12
+ class Diskio
13
+ attr_accessor :diskstats
14
+
15
+ def run(args)
16
+ host = args[:at]
17
+
18
+ if fixture_name = args[:fixture]
19
+ diskstats = parse_diskstats File.read(fixture_name)
20
+ else
21
+ diskio_then = parse_diskstats get_diskstats(host)
22
+ sleep 1
23
+ diskio_now = parse_diskstats get_diskstats(host)
24
+
25
+ # disk [read, write] stats
26
+ diskstats = [diskio_now[0]-diskio_then[0], diskio_now[1]-diskio_then[1]]
27
+ end
28
+
29
+ {:host => host, :diskio => diskstats}
30
+ end
31
+
32
+ private
33
+ def parse_diskstats(txt)
34
+ diskstats = txt.split(/\n/).collect { |x| x.strip }
35
+ rowcount = diskstats.count
36
+
37
+ rowcount.times do |i|
38
+ diskstats[i] = diskstats[i].gsub(/\s+/m, ' ').split(' ')
39
+ end
40
+
41
+ columns_array = []
42
+ rowcount.times do |i|
43
+ columns_array << [diskstats[i][3],diskstats[i][7]]
44
+ end
45
+
46
+ columncount = columns_array[0].count
47
+
48
+ total_read_writes = []
49
+ columncount.times do |i|
50
+ total_read_writes[i] = 0
51
+ end
52
+
53
+ columncount.times do |j|
54
+ rowcount.times do |k|
55
+ total_read_writes[j] = columns_array[k][j].to_i + total_read_writes[j]
56
+ end
57
+ end
58
+
59
+ total_read_writes
60
+ end
61
+
62
+
63
+ def get_diskstats(remote)
64
+ Net::SFTP.start(remote, @username, :keys => @pub_key) do |scp|
65
+ return scp.download!('/proc/diskstats')
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ require 'net/sftp'
2
+
3
+ module Silw
4
+ module Plugins
5
+
6
+ # Meminfo - report the memory stats collected from a remote system.
7
+ # For more details about meminfo see this featured article: /proc/meminfo Explained, at:
8
+ # - http://www.redhat.com/advice/tips/meminfo.html
9
+ #
10
+ # @return - a JSON containing the main memory stats
11
+ class Mem
12
+ attr_accessor :mem
13
+
14
+ def run(args)
15
+ host = args[:at]
16
+
17
+ if fixture_name = args[:fixture]
18
+ mem = parse_meminfo File.read(fixture_name)
19
+ else
20
+ mem = parse_meminfo get_meminfo(host)
21
+ end
22
+
23
+ {:host => host, :mem => mem}
24
+ end
25
+
26
+ private
27
+ def parse_meminfo(txt)
28
+ meminfo = txt.split(/\n/).collect{|x| x.strip}
29
+ memtotal = meminfo[0].gsub(/[^0-9]/, "").to_f
30
+ memfree = meminfo[1].gsub(/[^0-9]/, "").to_f
31
+ memactive = meminfo[5].gsub(/[^0-9]/, "").to_f
32
+ {:total => memtotal.round, :active => memactive.round, :free => memfree.round,
33
+ :usagepercentage => ((memactive * 100) / memtotal).round}
34
+ end
35
+
36
+ def get_meminfo(remote, opts={})
37
+ Net::SFTP.start(remote, @username, :keys => @pub_key ) do |scp|
38
+ return scp.download!('/proc/meminfo')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ require "sinatra/content_for"
2
+
3
+ module Silw
4
+ class Server < Sinatra::Base
5
+ STATIC_PATHS = ["/favicon.ico", "/img"]
6
+
7
+ app_dir = File.dirname(File.expand_path(__FILE__))
8
+
9
+ set :views, "#{app_dir}/views"
10
+ set :public_folder, "#{app_dir}/public"
11
+
12
+ use Rack::ShowExceptions
13
+ use Rack::MethodOverride
14
+
15
+ use Rack::StaticCache,
16
+ urls: STATIC_PATHS,
17
+ root: File.expand_path('./public/', __FILE__)
18
+
19
+ use Rack::Session::Cookie,
20
+ key: '<#>&wksjuIuwo!',
21
+ domain: "localhost",
22
+ path: '/',
23
+ expire_after: 14400,
24
+ secret: '*987^514('
25
+
26
+ helpers Sinatra::ContentFor
27
+ helpers Silw::Helpers
28
+
29
+ include Rack::Utils
30
+
31
+ configure do
32
+ disable :protection
33
+ enable :logging
34
+ end
35
+
36
+ get '/' do
37
+ # "Hello World! :#{settings.agents}:"
38
+ haml :index
39
+ end
40
+
41
+ # 404
42
+ not_found do
43
+ haml :"404"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Silw
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ .row
2
+ .span7
3
+ -unless settings.agents.empty?
4
+ .well
5
+ - settings.agents.each_value do |agent|
6
+ %table.table-striped
7
+ %thead
8
+ %tr
9
+ %th(width="20%" align="right" nowrap)
10
+ .span2
11
+ %span.label.label-important= agent.host
12
+ %th
13
+ -unless agent.plugins.empty?
14
+ -agent.plugins.each_key do |k|
15
+ %th(align="left")
16
+ %span.label.label= k
17
+ %tbody
18
+ -unless agent.plugins.empty?
19
+ %tr
20
+ %td(valign="top")
21
+ %small.label.label-inverse= agent.time
22
+ %td
23
+ %span
24
+ &nbsp; &nbsp;
25
+ -agent.plugins.each do |k, v|
26
+ %td(valign="top")
27
+ = partial k.to_s, :plugin => v
28
+ -else
29
+ pending ...
30
+
@@ -0,0 +1,50 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title= "SILW"
5
+ %meta{ :"http-equiv" => "content-type", :content => "text/html; charset=UTF-8" }
6
+ %meta{ :name => "viewport", :content => "width=device-width, initial-scale=1.0" }
7
+ %link{ :href => "//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css",
8
+ :media => "screen", :rel => "stylesheet", :type => "text/css" }
9
+ %body
10
+ .container-fluid
11
+ %header
12
+ %h2
13
+ %a{:href => '/'} SILW
14
+ %p.muted
15
+ Various system metrics frequently aggregated from multiple remote instances.
16
+ %p
17
+ autorefresh on/off
18
+ %button#button.btn.btn-mini
19
+ %a#refresh-switch(href="#")
20
+ %i.icon-stop
21
+ = yield
22
+
23
+ %footer
24
+ %p
25
+
26
+ %script{ :type => 'text/javascript', :src => "//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" }
27
+ %script{ :type => 'text/javascript', :src => "//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js" }
28
+ :javascript
29
+ $(document).ready(function () {
30
+ var interval = 10; //number of seconds before page refresh
31
+ var refresh = function() {
32
+ setTimeout(function() {
33
+ if( $("#refresh-switch > i").attr("class").match(/^icon-(.*)$/)[1] === "stop"){
34
+ location.reload(true);
35
+ $("#refresh-switch > i").attr("class", "icon-stop");
36
+ }
37
+ }, interval * 1000);
38
+ };
39
+
40
+ $("#button").click(function(e) {
41
+ if( $("#refresh-switch > i").attr("class").match(/^icon-(.*)$/)[1] === "stop"){
42
+ $("#refresh-switch > i").attr("class", "icon-play");
43
+ clearInterval(refresh);
44
+ }else{
45
+ location.reload(true);
46
+ }
47
+ });
48
+
49
+ refresh();
50
+ });
@@ -0,0 +1,9 @@
1
+ %ul.unstyled
2
+ - if plugin.is_a?(Hash)
3
+ - plugin.each_entry do |k, v|
4
+ %li
5
+ %small
6
+ %strong= "#{k}:"
7
+ %span= "#{v}%"
8
+ -else
9
+ %small.muted=plugin
@@ -0,0 +1,12 @@
1
+ %ul.unstyled
2
+ - unless plugin.nil?
3
+ %li
4
+ %small
5
+ %strong= "reads:"
6
+ %span= "#{plugin[0]}"
7
+ %li
8
+ %small
9
+ %strong= "writes:"
10
+ %span= "#{plugin[1]}"
11
+ -else
12
+ %small.muted=plugin
@@ -0,0 +1,12 @@
1
+ %ul.unstyled
2
+ - if plugin.is_a?(Hash)
3
+ - plugin.each_entry do |k, v|
4
+ %li
5
+ %small
6
+ %strong= "#{k}:"
7
+ -if k.match(/^usage/)
8
+ %span= "#{v}%"
9
+ -else
10
+ %span= like_filesize(v)
11
+ -else
12
+ %small.muted=plugin