silw 0.0.1

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.
@@ -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