bloopletech-webstats 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -8,10 +8,10 @@ begin
8
8
 
9
9
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
10
  s.authors = ["Brenton Fletcher"]
11
- s.date = %q{2009-04-22}
11
+ s.date = Date.today.strftime("%Y-%m-%d")
12
12
  s.description = s.summary = %q{Display server CPU/Memory/Disk Usage on a web page, suitable for remote performance monitoring.}
13
13
  s.email = %q{i@bloople.net}
14
- s.files = Dir['**/*']
14
+ s.files = Dir['**/*'].reject { |fn| fn =~ /(\.o|\.so|Makefile)$/ }
15
15
  s.executables = ['webstats']
16
16
  s.extensions = ["server/data_providers/extconf.rb"]
17
17
  s.has_rdoc = false
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 2
4
4
  :patch: 0
@@ -1,5 +1,7 @@
1
1
  class DataProviders::CpuInfo
2
- def initialize
2
+ def initialize(settings)
3
+ @settings = self.class.default_settings.merge(settings)
4
+
3
5
  @readings = []
4
6
  @mutex = Mutex.new
5
7
 
@@ -31,7 +33,7 @@ class DataProviders::CpuInfo
31
33
  last_idle = idle
32
34
  last_iowait = iowait
33
35
  last_time = time
34
- sleep(2.5)
36
+ sleep(@settings[:update_rate])
35
37
  end
36
38
  end
37
39
  end
@@ -41,8 +43,8 @@ class DataProviders::CpuInfo
41
43
  @mutex.synchronize do
42
44
  unless @readings.empty?
43
45
  out[:usage] = @readings.first
44
- out[:status] = 'warning' unless @readings.detect { |r| out[:usage] < 95 }
45
- out[:status] = 'danger' unless @readings.detect { |r| out[:usage] < 99.5 }
46
+ out[:status] = 'warning' unless @readings.detect { |r| out[:usage] < @settings[:usage_warning_level] }
47
+ out[:status] = 'danger' unless @readings.detect { |r| out[:usage] < @settings[:usage_danger_level] }
46
48
  end
47
49
  end
48
50
  out[:loadavg_1], out[:loadavg_5], out[:loadavg_15] = IO.readlines("/proc/loadavg").first.split(' ', 4).map { |v| v.to_f }
@@ -58,12 +60,12 @@ sc.innerHTML = "<div class='major_figure'><span class='title'>Usage</span><span
58
60
  } })
59
61
  end
60
62
 
61
- def information
62
- { :name => "CPU Info", :in_sentence => 'CPU load', :importance => importance }
63
+ def self.default_settings
64
+ { :update_rate => 2.5, :usage_warning_level => 95, :usage_danger_level => 99.5 }
63
65
  end
64
66
 
65
- def importance
66
- 100
67
+ def information
68
+ { :name => "CPU Info", :in_sentence => 'CPU load', :importance => 100 }
67
69
  end
68
70
 
69
71
  def kill
@@ -1,5 +1,7 @@
1
1
  class DataProviders::DiskActivity
2
- def initialize
2
+ def initialize(settings)
3
+ @settings = self.class.default_settings.merge(settings)
4
+
3
5
  @reads_sec = 0
4
6
  @writes_sec = 0
5
7
 
@@ -20,7 +22,7 @@ class DataProviders::DiskActivity
20
22
  last_reads = reads
21
23
  last_writes = writes
22
24
  last_time = time
23
- sleep(2.5)
25
+ sleep(@settings[:update_rate])
24
26
  end
25
27
  end
26
28
  end
@@ -30,18 +32,18 @@ class DataProviders::DiskActivity
30
32
  end
31
33
 
32
34
  def renderer
33
- information.merge({ :name => "Disk Activity", :in_sentence => "Disk Activity", :importance => importance, :contents => %{
35
+ information.merge({ :contents => %{
34
36
  sc.innerHTML = "<div class='major_figure'><span class='title'>Reads</span><span class='figure'>" + data_source['reads'] + "</span><span class='unit'>mb/s</span></div>" +
35
37
  "<div class='major_figure'><span class='title'>Writes</span><span class='figure'>" + data_source['writes'] + "</span><span class='unit'>mb/s</span></div>";
36
38
  } })
37
39
  end
38
40
 
39
- def information
40
- { :name => "Disk Activity", :in_sentence => "Disk Activity", :importance => importance }
41
+ def self.default_settings
42
+ { :update_rate => 2.5 }
41
43
  end
42
44
 
43
- def importance
44
- 70
45
+ def information
46
+ { :name => "Disk Activity", :in_sentence => "Disk Activity", :importance => 70 }
45
47
  end
46
48
 
47
49
  def kill
@@ -1,5 +1,6 @@
1
1
  class DataProviders::DiskUsage
2
- def initialize
2
+ def initialize(settings)
3
+ @settings = self.class.default_settings.merge(settings)
3
4
  end
4
5
 
5
6
  def get
@@ -16,15 +17,15 @@ class DataProviders::DiskUsage
16
17
  out[:mounts].map do |mp|
17
18
  mp[1]['free'] /= (1024.0 * 1024)
18
19
  mp[1]['total'] /= (1024.0 * 1024)
19
- out[:status] = "warning" if mp[1]['free'] < 50 and mp[1]['total'] > 100 and out[:status] != 'danger'
20
- out[:status] = "danger" if mp[1]['free'] < 10 and mp[1]['total'] > 20
20
+ out[:status] = "warning" if mp[1]['free'] < @settings[:warning_threshold] and mp[1]['total'] > @settings[:warning_minimum_mount_point_size] and out[:status] != 'danger'
21
+ out[:status] = "danger" if mp[1]['free'] < @settings[:danger_threshold] and mp[1]['total'] > @settings[:danger_minimum_mount_point_size]
21
22
  end
22
23
 
23
24
  out
24
25
  end
25
26
 
26
27
  def renderer
27
- information.merge({ :name => "Disk Usage by Mount Point", :in_sentence => "Disk Usage", :importance => importance, :contents => %{
28
+ information.merge({ :contents => %{
28
29
  var temp = "";
29
30
  for(var i = 0; i < data_source['mounts'].length; i++)
30
31
  {
@@ -38,12 +39,12 @@ sc.innerHTML = temp;
38
39
  } })
39
40
  end
40
41
 
41
- def information
42
- { :name => "Disk Usage by Mount Point", :in_sentence => "Disk Usage", :importance => importance }
42
+ def self.default_settings
43
+ { :warning_threshold => 50, :danger_threshold => 10, :warning_minimum_mount_point_size => 100, :danger_minimum_mount_point_size => 20 }
43
44
  end
44
45
 
45
- def importance
46
- 80
46
+ def information
47
+ { :name => "Disk Usage by Mount Point", :in_sentence => "Disk Usage", :importance => 80 }
47
48
  end
48
49
 
49
50
  def kill
@@ -1,5 +1,7 @@
1
1
  class DataProviders::MemInfo
2
- def initialize
2
+ def initialize(settings)
3
+ @settings = self.class.default_settings.merge(settings)
4
+
3
5
  @readings = []
4
6
  @mutex = Mutex.new
5
7
 
@@ -13,7 +15,7 @@ class DataProviders::MemInfo
13
15
  @readings.unshift(out)
14
16
  @readings.pop while @readings.length > 5
15
17
  end
16
- sleep(2.5)
18
+ sleep(@settings[:update_rate])
17
19
  end
18
20
  end
19
21
  end
@@ -38,12 +40,12 @@ sc.innerHTML = "<div class='major_figure'><span class='title'>Free</span><span c
38
40
  } })
39
41
  end
40
42
 
41
- def information
42
- { :name => "Memory Info", :in_sentence => 'Memory Usage', :importance => importance }
43
+ def self.default_settings
44
+ { :update_rate => 2.5 }
43
45
  end
44
46
 
45
- def importance
46
- 90
47
+ def information
48
+ { :name => "Memory Info", :in_sentence => 'Memory Usage', :importance => 90 }
47
49
  end
48
50
 
49
51
  def kill
@@ -0,0 +1,67 @@
1
+ require 'net/http'
2
+
3
+ class DataProviders::UrlMonitor
4
+ def initialize(settings)
5
+ @settings = self.class.default_settings.merge(settings)
6
+
7
+ @readings = []
8
+
9
+ @mutex = Mutex.new
10
+
11
+ @thread = Thread.new do
12
+ while(true)
13
+ @mutex.synchronize { @readings = [] }
14
+
15
+ @settings[:urls].sort.each do |url|
16
+ duration = -1
17
+ works = false
18
+ begin
19
+ start = Time.now
20
+ result = Net::HTTP.get(URI.parse(url))
21
+ duration = Time.now - start
22
+ works = true
23
+ rescue Exception => e
24
+ end
25
+ @mutex.synchronize { @readings << [url, { :response_time => duration * 1000, :works => works }] }
26
+ end
27
+ sleep(@settings[:update_rate])
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ def get
34
+ out = {}
35
+ @mutex.synchronize { out[:urls] = @readings }
36
+ out[:urls].each do |(url, info)|
37
+ out[:status] = 'warning' if !info[:works] or info[:response_time] > @settings[:warning_response_time_threshold] and !out[:status] == 'danger'
38
+ out[:status] = 'danger' if !info[:works] or info[:response_time] > @settings[:danger_response_time_threshold]
39
+ end
40
+ out
41
+ end
42
+
43
+ def renderer
44
+ information.merge({ :contents => %{
45
+ var temp = "";
46
+ for(var i = 0; i < data_source['urls'].length; i++)
47
+ {
48
+ var ud = data_source['urls'][i][1];
49
+ temp += "<div class='major_figure'><span class='title'>" + data_source['urls'][i][0] + "</span><span class='figure'>" +
50
+ (!ud['works'] ? 'Failed</span>' : ud['response_time'] + "</span><span class='unit'>ms</span>") + "</div>";
51
+ }
52
+
53
+ sc.innerHTML = temp;
54
+ } })
55
+ end
56
+
57
+ def self.default_settings
58
+ { :update_rate => 30, :warning_response_time_threshold => 5000, :danger_response_time_threshold => 30000, :urls => ['http://localhost/'] }
59
+ end
60
+
61
+ def information
62
+ { :name => "URL Monitor", :in_sentence => "URL Monitor", :importance => 60 }
63
+ end
64
+
65
+ def kill
66
+ end
67
+ end
data/server/webstats.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'webrick'
2
+ require 'yaml'
2
3
 
3
4
  if $DEBUG
4
5
  Thread.abort_on_exception
@@ -15,6 +16,24 @@ Thread.new do
15
16
  end
16
17
  end
17
18
 
19
+ class NilClass
20
+ def to_json
21
+ "null"
22
+ end
23
+ end
24
+
25
+ class TrueClass
26
+ def to_json
27
+ "true"
28
+ end
29
+ end
30
+
31
+ class FalseClass
32
+ def to_json
33
+ "false"
34
+ end
35
+ end
36
+
18
37
  class String
19
38
  def underscore
20
39
  self.gsub(/::/, '/').
@@ -62,17 +81,39 @@ class Symbol
62
81
  end
63
82
 
64
83
  module DataProviders
84
+ DATA_SOURCES_CLASSES = {}
65
85
  DATA_SOURCES = {}
66
- def self.setup
86
+ def self.preload
67
87
  Dir.glob("#{File.dirname(__FILE__)}/data_providers/*.rb").each { |file| load file }
68
88
  DataProviders.constants.each do |c|
69
89
  c = DataProviders.const_get(c)
70
- DATA_SOURCES[c.to_s.gsub(/^DataProviders::/, '').underscore] = c.new if c.is_a? Class
90
+ DATA_SOURCES_CLASSES[c.to_s.gsub(/^DataProviders::/, '').underscore] = c if c.is_a? Class
71
91
  end
72
92
  end
93
+ def self.setup(settings)
94
+ DATA_SOURCES_CLASSES.each_pair { |k, v| DATA_SOURCES[k] = v.new(settings[k]) }
95
+ end
96
+ end
97
+
98
+ DataProviders.preload
99
+
100
+ WEBSTATS_PATH = File.expand_path("~/.webstats")
101
+
102
+ settings = {}
103
+
104
+ if File.exists?(WEBSTATS_PATH)
105
+ settings = YAML.load(IO.read(WEBSTATS_PATH))
106
+ else
107
+ DataProviders::DATA_SOURCES_CLASSES.each_pair do |k, v|
108
+ settings[k] = v.default_settings
109
+ end
110
+
111
+ File.open(WEBSTATS_PATH, "w") do |f|
112
+ YAML.dump(settings, f)
113
+ end
73
114
  end
74
115
 
75
- DataProviders.setup
116
+ DataProviders.setup(settings)
76
117
 
77
118
  class Webstats < WEBrick::HTTPServlet::AbstractServlet
78
119
  def do_GET(req, res)
@@ -144,7 +185,7 @@ body << <<-EOF
144
185
  <div id="main">
145
186
  <h1><span>Stats for #{req.host}</span></h1>
146
187
  EOF
147
- DataProviders::DATA_SOURCES.sort { |a, b| b[1].importance <=> a[1].importance }.each do |(k, v)|
188
+ DataProviders::DATA_SOURCES.sort { |a, b| b[1].information[:importance] <=> a[1].information[:importance] }.each do |(k, v)|
148
189
  r = v.renderer
149
190
  body << %{<div class="source" id="source_#{k}"><h2><span>#{r[:name]}</span></h2><div class="source_contents" id="source_contents_#{k}">Loading...</div></div>}
150
191
  end
@@ -157,7 +198,7 @@ EOF
157
198
  elsif req.path_info == '/update'
158
199
  out = {}
159
200
  DataProviders::DATA_SOURCES.each_pair do |k, v|
160
- out[k] = v.get
201
+ out[k] = v.get.dup
161
202
  end
162
203
 
163
204
  fix_leaves_hash(out)
@@ -179,9 +220,9 @@ EOF
179
220
  if v.is_a? Numeric
180
221
  array[i] = v.formatted
181
222
  elsif v.is_a? Hash
182
- array[i] = fix_leaves_hash(array[i])
223
+ array[i] = fix_leaves_hash(array[i].dup)
183
224
  elsif v.is_a? Array
184
- array[i] = fix_leaves_array(array[i])
225
+ array[i] = fix_leaves_array(array[i].dup)
185
226
  end
186
227
  end
187
228
  end
@@ -191,9 +232,9 @@ EOF
191
232
  if v.is_a? Numeric
192
233
  hash[k] = v.formatted
193
234
  elsif v.is_a? Hash
194
- hash[k] = fix_leaves_hash(hash[k])
235
+ hash[k] = fix_leaves_hash(hash[k].dup)
195
236
  elsif v.is_a? Array
196
- hash[k] = fix_leaves_array(hash[k])
237
+ hash[k] = fix_leaves_array(hash[k].dup)
197
238
  end
198
239
  end
199
240
  end
data/webstats.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{webstats}
5
- s.version = "0.1.0"
5
+ s.version = "0.2.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Brenton Fletcher"]
9
- s.date = %q{2009-04-22}
9
+ s.date = %q{2009-04-25}
10
10
  s.default_executable = %q{webstats}
11
11
  s.description = %q{Display server CPU/Memory/Disk Usage on a web page, suitable for remote performance monitoring.}
12
12
  s.email = %q{i@bloople.net}
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  "server/data_providers/disk_usage.rb",
32
32
  "server/data_providers/extconf.rb",
33
33
  "server/data_providers/mem_info.rb",
34
+ "server/data_providers/url_monitor.rb",
34
35
  "server/webstats.rb",
35
36
  "webstats.gemspec"
36
37
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bloopletech-webstats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brenton Fletcher
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-22 00:00:00 -07:00
12
+ date: 2009-04-25 00:00:00 -07:00
13
13
  default_executable: webstats
14
14
  dependencies: []
15
15
 
@@ -37,6 +37,7 @@ files:
37
37
  - server/data_providers/disk_usage.rb
38
38
  - server/data_providers/extconf.rb
39
39
  - server/data_providers/mem_info.rb
40
+ - server/data_providers/url_monitor.rb
40
41
  - server/webstats.rb
41
42
  - webstats.gemspec
42
43
  has_rdoc: false