spectacular 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.testlib = 'minitest/autorun'
7
+ at.find_directories = ARGV unless ARGV.empty?
8
+ end
File without changes
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2012-03-25
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,15 @@
1
+ .autotest
2
+ CHANGELOG.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ bin/spectacular
7
+ lib/spectacular.rb
8
+ lib/spectacular/device.rb
9
+ lib/spectacular/history.rb
10
+ lib/spectacular/index.html
11
+ lib/spectacular/server.rb
12
+ lib/spectacular/sized_list.rb
13
+ test/helper.rb
14
+ test/test_history.rb
15
+ test/test_sized_list.rb
@@ -0,0 +1,52 @@
1
+ = spectacular
2
+
3
+ * http://github.com/tenderlove/spectacular
4
+
5
+ == DESCRIPTION:
6
+
7
+ Spectacular is an SNMP client that displays traffic information in the
8
+ browser.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * It updates every second
13
+ * I don't know what the units are on the Y axis yet
14
+ * X axis is time
15
+
16
+ == SYNOPSIS:
17
+
18
+ $ gem install spectacular
19
+ FIX (code sample of usage)
20
+
21
+ == REQUIREMENTS:
22
+
23
+ * FIX (list of requirements)
24
+
25
+ == INSTALL:
26
+
27
+ * FIX (sudo gem install, anything else)
28
+
29
+ == LICENSE:
30
+
31
+ (The MIT License)
32
+
33
+ Copyright (c) 2012 Aaron Patterson
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining
36
+ a copy of this software and associated documentation files (the
37
+ 'Software'), to deal in the Software without restriction, including
38
+ without limitation the rights to use, copy, modify, merge, publish,
39
+ distribute, sublicense, and/or sell copies of the Software, and to
40
+ permit persons to whom the Software is furnished to do so, subject to
41
+ the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be
44
+ included in all copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
47
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
48
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
49
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
50
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
51
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
52
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugins.delete :rubyforge
7
+ Hoe.plugin :minitest
8
+ Hoe.plugin :gemspec # `gem install hoe-gemspec`
9
+ Hoe.plugin :git # `gem install hoe-git`
10
+
11
+ Hoe.spec 'spectacular' do
12
+ developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
13
+ self.readme_file = 'README.rdoc'
14
+ self.history_file = 'CHANGELOG.rdoc'
15
+ self.extra_rdoc_files = FileList['*.rdoc']
16
+ self.extra_deps << ['snmp', '~> 1.1.0']
17
+ end
18
+
19
+ # vim: syntax=ruby
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spectacular/server'
4
+
5
+ server = WEBrick::HTTPServer.new :Port => 8080, :OutputBufferSize => 256
6
+
7
+ server.mount '/', Spectacular::Server::Index
8
+ server.mount '/events.json', Spectacular::Server::Events
9
+
10
+ trap('INT') { server.shutdown }
11
+ server.start
@@ -0,0 +1,7 @@
1
+ require 'spectacular/sized_list'
2
+ require 'spectacular/history'
3
+ require 'spectacular/device'
4
+
5
+ module Spectacular
6
+ VERSION = '1.0.0'
7
+ end
@@ -0,0 +1,69 @@
1
+ require 'spectacular/history'
2
+ require 'snmp'
3
+ require 'json'
4
+
5
+ module Spectacular
6
+ class Device
7
+ attr_reader :host, :history
8
+
9
+ def initialize host, history = 10
10
+ @host = host
11
+ @history = new_history history
12
+ end
13
+
14
+ def columns
15
+ ["ifIndex", "ifDescr", "ifInOctets", "ifOutOctets"]
16
+ end
17
+
18
+ def interfaces
19
+ manager = snmp_manager host
20
+ interfaces = []
21
+ manager.walk(['ifDescr']) do |row|
22
+ interfaces << row.first.value.to_s
23
+ end
24
+ interfaces
25
+ ensure
26
+ manager.close
27
+ end
28
+
29
+ def monitor interval = 1
30
+ manager = snmp_manager host
31
+
32
+ loop do
33
+ manager.walk(columns) do |row|
34
+ key = row[1].value.to_s
35
+
36
+ result = diff history.last(key), row
37
+
38
+ yield result unless result.empty?
39
+
40
+ history.add key, row.map(&:dup)
41
+ end
42
+ sleep interval
43
+ end
44
+
45
+ ensure
46
+ manager.close
47
+ end
48
+
49
+ private
50
+ def snmp_manager host
51
+ SNMP::Manager.new :Host => host
52
+ end
53
+
54
+ Klass = Struct.new :name, :value # :nodoc:
55
+
56
+ def diff from, to
57
+ return [] unless from && to
58
+
59
+ to.first(2) + from.zip(to).last(2).map { |left, right|
60
+ diff = right.value.to_i - left.value.to_i
61
+ Klass.new left.name, diff
62
+ }
63
+ end
64
+
65
+ def new_history size
66
+ History.new size
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ require 'spectacular/sized_list'
2
+
3
+ module Spectacular
4
+ class History
5
+ def initialize size = 10
6
+ @groups = Hash.new { |h,k|
7
+ h[k] = new_list size
8
+ }
9
+ end
10
+
11
+ def add k, v
12
+ @groups[k] << v
13
+ end
14
+
15
+ def last k, len = nil
16
+ if len
17
+ @groups[k].last len
18
+ else
19
+ @groups[k].last
20
+ end
21
+ end
22
+
23
+ def [] k
24
+ @groups[k].to_a
25
+ end
26
+
27
+ private
28
+ def new_list size
29
+ SizedList.new size
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,68 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
5
+
6
+ <script>
7
+ function options(interface) {
8
+ return { 'title': interface,
9
+ 'width':1000,
10
+ 'height':300,
11
+ 'hAxis': { 'title': 'Time' },
12
+ 'vAxis': { 'title': 'Data' } };
13
+ }
14
+
15
+ function listen() {
16
+ var charts = document.getElementById('interfaces');
17
+ var dataTables = {}
18
+ var gCharts = {}
19
+
20
+ if (!!window.EventSource) {
21
+ var source = new EventSource('/events.json');
22
+ source.addEventListener('interfaces', function(e) {
23
+ var data = JSON.parse(e.data);
24
+ data.forEach(function(name) {
25
+ var chart = document.createElement('div');
26
+ chart.setAttribute('id', name);
27
+ charts.appendChild(chart);
28
+
29
+ var gchart = new google.visualization.LineChart(chart);
30
+ var table = new google.visualization.DataTable();
31
+ table.addColumn('string', 'time');
32
+ table.addColumn('number', 'input');
33
+ table.addColumn('number', 'output');
34
+
35
+ gCharts[name] = gchart;
36
+ dataTables[name] = table;
37
+ });
38
+ }, false);
39
+
40
+ source.addEventListener('update', function(e) {
41
+ var data = JSON.parse(e.data);
42
+ var chart = gCharts[data.interface];
43
+ var table = dataTables[data.interface];
44
+
45
+ if(chart && table) {
46
+ var count = table.getNumberOfRows();
47
+ table.addRow(['', data.in_delta, data.out_delta]);
48
+ if(count > 60) {
49
+ table.removeRow(0);
50
+ }
51
+ chart.draw(table, options(data.interface));
52
+ }
53
+ }, false);
54
+ } else {
55
+ alert('your browser is not supported');
56
+ }
57
+ }
58
+
59
+ google.load('visualization', '1.0', {'packages':['corechart']});
60
+ google.load("jquery", "1.7.1");
61
+ google.setOnLoadCallback(listen);
62
+ </script>
63
+ </head>
64
+ <body>
65
+ <div id="interfaces">
66
+ </div>
67
+ </body>
68
+ </html>
@@ -0,0 +1,53 @@
1
+ require 'webrick'
2
+ require 'spectacular'
3
+ require 'json'
4
+
5
+ module Spectacular
6
+ module Server
7
+ dir = File.dirname __FILE__
8
+ INDEX = File.read File.expand_path File.join dir, 'index.html'
9
+
10
+ class Index < WEBrick::HTTPServlet::AbstractServlet
11
+ def do_GET request, response
12
+ response.status = 200
13
+ response['Content-Type'] = 'text/html'
14
+ response.body = INDEX
15
+ end
16
+ end
17
+
18
+ class Events < WEBrick::HTTPServlet::AbstractServlet
19
+ def do_GET request, response
20
+ response.status = 200
21
+ response['Content-Type'] = 'text/event-stream'
22
+ rd, rw = IO.pipe
23
+
24
+ response.body = rd
25
+
26
+ Thread.new {
27
+ begin
28
+ ip = ENV['ROUTER'] || '10.0.1.1'
29
+ dev = Spectacular::Device.new ip
30
+
31
+ rw.write "event: interfaces\n"
32
+ rw.write "data: #{JSON.dump(dev.interfaces)}\n\n"
33
+ rw.flush
34
+
35
+ dev.monitor do |row|
36
+ data = {
37
+ :interface => row[1].value.to_s,
38
+ :in_delta => row[2].value,
39
+ :out_delta => row[3].value,
40
+ }
41
+
42
+ rw.write "event: update\n"
43
+ rw.write "data: #{JSON.dump(data)}\n\n"
44
+ rw.flush
45
+ end
46
+ ensure
47
+ rw.close
48
+ end
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,32 @@
1
+ module Spectacular
2
+ class SizedList
3
+ include Enumerable
4
+
5
+ def initialize size
6
+ @size = size
7
+ @list = []
8
+ end
9
+
10
+ def << item
11
+ x = @list << item
12
+ while @list.size > @size
13
+ @list.shift
14
+ end
15
+ x
16
+ end
17
+
18
+ def each &block
19
+ @list.each(&block)
20
+ end
21
+
22
+ def last n = nil
23
+ return @list.last unless n
24
+
25
+ @list.last n
26
+ end
27
+
28
+ def length
29
+ @list.length
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ require 'minitest/autorun'
2
+ require 'spectacular'
3
+
4
+ module Spectacular
5
+ class TestCase < MiniTest::Unit::TestCase
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ module Spectacular
4
+ class TestHistory < TestCase
5
+ def test_add
6
+ history = History.new
7
+ history.add "foo", "bar"
8
+ assert_equal "bar", history.last("foo")
9
+ end
10
+
11
+ def test_last_2
12
+ history = History.new
13
+ history.add "foo", "bar"
14
+ history.add "foo", "baz"
15
+ assert_equal ["bar", "baz"], history.last("foo", 2)
16
+ end
17
+
18
+ def test_all
19
+ history = History.new
20
+ history.add "foo", "bar"
21
+ history.add "foo", "baz"
22
+ assert_equal ['bar', 'baz'], history['foo']
23
+ end
24
+
25
+ def test_key_miss
26
+ history = History.new
27
+ history.add "foo", "bar"
28
+ history.add "foo", "baz"
29
+ assert_equal [], history['bar']
30
+ assert_nil history.last 'bar'
31
+ assert_equal [], history.last('bar', 2)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ module Spectacular
4
+ class TestSizedList < TestCase
5
+ def test_first
6
+ list = SizedList.new 10
7
+ 11.times { |i| list << i }
8
+ assert_equal 1, list.first
9
+ end
10
+
11
+ def test_last
12
+ list = SizedList.new 10
13
+ 11.times { |i| list << i }
14
+ assert_equal 10, list.last
15
+ end
16
+
17
+ def test_length
18
+ list = SizedList.new 10
19
+ assert_equal 0, list.length
20
+ 10.times { |i|
21
+ list << i
22
+ assert_equal(i + 1, list.length)
23
+ }
24
+ list << Object.new
25
+ assert_equal 10, list.length
26
+ end
27
+
28
+ def test_last_param
29
+ list = SizedList.new 10
30
+ 10.times { |i| list << i }
31
+ assert_equal [7, 8, 9], list.last(3)
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spectacular
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Patterson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: snmp
16
+ requirement: &70259861651520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70259861651520
25
+ - !ruby/object:Gem::Dependency
26
+ name: minitest
27
+ requirement: &70259861642360 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.11'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70259861642360
36
+ - !ruby/object:Gem::Dependency
37
+ name: rdoc
38
+ requirement: &70259861641620 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '3.10'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70259861641620
47
+ - !ruby/object:Gem::Dependency
48
+ name: hoe
49
+ requirement: &70259861640980 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.13'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70259861640980
58
+ description: ! 'Spectacular is an SNMP client that displays traffic information in
59
+ the
60
+
61
+ browser.'
62
+ email:
63
+ - aaron@tenderlovemaking.com
64
+ executables:
65
+ - spectacular
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - Manifest.txt
69
+ - CHANGELOG.rdoc
70
+ - README.rdoc
71
+ files:
72
+ - .autotest
73
+ - CHANGELOG.rdoc
74
+ - Manifest.txt
75
+ - README.rdoc
76
+ - Rakefile
77
+ - bin/spectacular
78
+ - lib/spectacular.rb
79
+ - lib/spectacular/device.rb
80
+ - lib/spectacular/history.rb
81
+ - lib/spectacular/index.html
82
+ - lib/spectacular/server.rb
83
+ - lib/spectacular/sized_list.rb
84
+ - test/helper.rb
85
+ - test/test_history.rb
86
+ - test/test_sized_list.rb
87
+ - .gemtest
88
+ homepage: http://github.com/tenderlove/spectacular
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --main
93
+ - README.rdoc
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project: spectacular
110
+ rubygems_version: 1.8.17
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Spectacular is an SNMP client that displays traffic information in the browser.
114
+ test_files:
115
+ - test/test_history.rb
116
+ - test/test_sized_list.rb