pimon 0.1.0
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/.gitignore +10 -0
- data/.travis.yml +6 -0
- data/Capfile +2 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +77 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/WTFPL-LICENSE +13 -0
- data/bin/clean_redis_stats +1 -0
- data/bin/free.c +12 -0
- data/bin/makefile +2 -0
- data/bin/pimon +19 -0
- data/bin/random.c +21 -0
- data/bin/random.h +1 -0
- data/bin/vmstat.c +12 -0
- data/config/config.ru +4 -0
- data/config/config_dev.ru +11 -0
- data/config/deploy.rb +65 -0
- data/config/development.yml +25 -0
- data/config/production.yml +27 -0
- data/config/test.yml +27 -0
- data/config/test_broken.yml +22 -0
- data/config/thin/config.yml +13 -0
- data/lib/pimon.rb +63 -0
- data/lib/pimon/hash_extensions.rb +12 -0
- data/lib/pimon/pimon_config.rb +75 -0
- data/lib/pimon/probe/cpu_usage.rb +11 -0
- data/lib/pimon/probe/disk_usage.rb +11 -0
- data/lib/pimon/probe/memory_usage.rb +13 -0
- data/lib/pimon/probe/probe.rb +13 -0
- data/lib/pimon/probe/swap_usage.rb +13 -0
- data/lib/pimon/probe/system_memory.rb +19 -0
- data/lib/pimon/probe/temperature.rb +16 -0
- data/lib/pimon/public/favicon.ico +0 -0
- data/lib/pimon/stats_collector.rb +65 -0
- data/lib/pimon/version.rb +3 -0
- data/lib/pimon/views/index.haml +10 -0
- data/lib/pimon/views/index.js.haml +111 -0
- data/pimon.gemspec +34 -0
- data/spec/pimon_config_spec.rb +34 -0
- data/spec/pimon_spec.rb +35 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/stats_collector_spec.rb +61 -0
- metadata +272 -0
data/config/test.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
basic_auth:
|
2
|
+
enabled: true
|
3
|
+
username: pimon
|
4
|
+
password: pimon
|
5
|
+
redis:
|
6
|
+
socket: /thou/shalt/not/use/redis/on/test/environment
|
7
|
+
chart:
|
8
|
+
cpu:
|
9
|
+
color: '#D2691E'
|
10
|
+
disk:
|
11
|
+
color: '#CDC673'
|
12
|
+
mem:
|
13
|
+
color: '#87CEFA'
|
14
|
+
temp:
|
15
|
+
color: '#FF9B04'
|
16
|
+
swap:
|
17
|
+
color: '#3CB371'
|
18
|
+
queues:
|
19
|
+
time: pimon_time
|
20
|
+
cpu: pimon_cpu
|
21
|
+
disk: pimon_disk
|
22
|
+
mem: pimon_mem
|
23
|
+
swap: pimon_swap
|
24
|
+
temp: pimon_temp
|
25
|
+
stats_collector:
|
26
|
+
number_of_stats: 6
|
27
|
+
time_period_in_min: 10
|
@@ -0,0 +1,22 @@
|
|
1
|
+
basic_auth:
|
2
|
+
enabled: true
|
3
|
+
redis:
|
4
|
+
socket: /thou/shalt/not/use/redis/on/test/environment
|
5
|
+
chart:
|
6
|
+
cpu:
|
7
|
+
color: '#D2691E'
|
8
|
+
disk:
|
9
|
+
color: '#CDC673'
|
10
|
+
mem:
|
11
|
+
color: '#87CEFA'
|
12
|
+
swap:
|
13
|
+
color: '#3CB371'
|
14
|
+
queues:
|
15
|
+
time: pimon_time
|
16
|
+
cpu: pimon_cpu
|
17
|
+
disk: pimon_disk
|
18
|
+
mem: pimon_mem
|
19
|
+
swap: pimon_swap
|
20
|
+
stats_collector:
|
21
|
+
number_of_stats: 6
|
22
|
+
time_period_in_min: 10
|
@@ -0,0 +1,13 @@
|
|
1
|
+
environment: production
|
2
|
+
chdir: /home/pi/app/pimon/current
|
3
|
+
address: 127.0.0.1
|
4
|
+
user: pi
|
5
|
+
group: pi
|
6
|
+
port: 4567
|
7
|
+
pid: /home/pi/app/pimon/shared/pids/thin.pid
|
8
|
+
rackup: /home/pi/app/pimon/current/config/config.ru
|
9
|
+
log: /home/pi/app/pimon/shared/log/thin.log
|
10
|
+
max_conns: 1024
|
11
|
+
timeout: 30
|
12
|
+
max_persistent_conns: 512
|
13
|
+
daemonize: true
|
data/lib/pimon.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
|
3
|
+
if File.file?("Gemfile")
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.require(:default, ENV['RACK_ENV'])
|
6
|
+
end
|
7
|
+
require 'eventmachine'
|
8
|
+
require 'haml'
|
9
|
+
require 'pimon/pimon_config'
|
10
|
+
require 'pimon/stats_collector'
|
11
|
+
require 'sinatra'
|
12
|
+
|
13
|
+
class Pimon < Sinatra::Base
|
14
|
+
set :public_folder, "#{File.dirname(__FILE__)}/pimon/public"
|
15
|
+
set :views, "#{File.dirname(__FILE__)}/pimon/views"
|
16
|
+
|
17
|
+
def self.configure_basic_auth
|
18
|
+
if settings.config.is_basic_auth_enabled?
|
19
|
+
use Rack::Auth::Basic, "Restricted Area" do |username, password|
|
20
|
+
[username, password] == config.basic_auth
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
configure :development, :production do
|
26
|
+
require 'redis'
|
27
|
+
|
28
|
+
config = PimonConfig.create_new(ENV['RACK_ENV'])
|
29
|
+
|
30
|
+
EventMachine::next_tick do
|
31
|
+
settings.timer = EventMachine::add_periodic_timer(config.stats[:time_period_in_min] * 60) do
|
32
|
+
settings.stats_checker.collect_stats
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
set :config, config
|
37
|
+
set :stats_checker, StatsCollector.new(config, Redis.new(:path => config.redis[:socket]))
|
38
|
+
set :timer, nil
|
39
|
+
|
40
|
+
self.configure_basic_auth
|
41
|
+
end
|
42
|
+
|
43
|
+
configure :test do
|
44
|
+
require 'mock_redis'
|
45
|
+
|
46
|
+
config = PimonConfig.create_new('test')
|
47
|
+
|
48
|
+
set :config, config
|
49
|
+
set :stats_checker, StatsCollector.new(config, MockRedis.new)
|
50
|
+
set :timer, nil
|
51
|
+
|
52
|
+
self.configure_basic_auth
|
53
|
+
end
|
54
|
+
|
55
|
+
get '/' do
|
56
|
+
last_update = settings.stats_checker.last_update
|
57
|
+
last_modified(last_update) if ENV['RACK_ENV'] != 'development' && last_update
|
58
|
+
|
59
|
+
@o = settings.stats_checker.show_stats
|
60
|
+
|
61
|
+
haml :index
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# As seen in https://gist.github.com/151324
|
2
|
+
module HashExtensions
|
3
|
+
def symbolize_keys
|
4
|
+
inject({}) do |acc, (k,v)|
|
5
|
+
key = String === k ? k.to_sym : k
|
6
|
+
value = Hash === v ? v.symbolize_keys : v
|
7
|
+
acc[key] = value
|
8
|
+
acc
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
Hash.send(:include, HashExtensions)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'pimon/hash_extensions'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class PimonConfig
|
5
|
+
def self.create_new(environment = nil)
|
6
|
+
config = self.new(environment)
|
7
|
+
|
8
|
+
return config if config.valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
def basic_auth
|
12
|
+
if is_basic_auth_enabled? && has_authentication_details?
|
13
|
+
[@config[:basic_auth][:username], @config[:basic_auth][:password]]
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def chart
|
20
|
+
@config[:chart]
|
21
|
+
end
|
22
|
+
|
23
|
+
def environment
|
24
|
+
@config[:environment]
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_basic_auth_enabled?
|
28
|
+
@config[:basic_auth][:enabled]
|
29
|
+
end
|
30
|
+
|
31
|
+
def queues
|
32
|
+
@config[:queues]
|
33
|
+
end
|
34
|
+
|
35
|
+
def redis
|
36
|
+
@config[:redis]
|
37
|
+
end
|
38
|
+
|
39
|
+
def stats
|
40
|
+
@config[:stats_collector]
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
raise "Basic auth enabled with no authentication details" if is_basic_auth_enabled? && !has_authentication_details?
|
45
|
+
raise "Redis has no socket details" unless has_redis_details?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def initialize(environment)
|
52
|
+
begin
|
53
|
+
filename = "#{File.dirname(__FILE__)}/../../config/#{ environment || 'development' }.yml"
|
54
|
+
@config = YAML.load_file(filename).symbolize_keys
|
55
|
+
@config.merge!({ :environment => "#{ environment || 'development'}"})
|
56
|
+
@config.freeze
|
57
|
+
rescue Exception => e
|
58
|
+
puts "Error while loading config file: #{filename}"
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_authentication_details?
|
64
|
+
@config[:basic_auth].has_key?(:username) && @config[:basic_auth].has_key?(:password)
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_redis_details?
|
68
|
+
# Thou shalt not use a live redis in test environment
|
69
|
+
is_test_environment? || (@config.has_key?(:redis) && @config[:redis].has_key?(:socket))
|
70
|
+
end
|
71
|
+
|
72
|
+
def is_test_environment?
|
73
|
+
@config[:environment] == 'test'
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class SystemMemory
|
2
|
+
|
3
|
+
def self.check(type)
|
4
|
+
case type
|
5
|
+
when :mem
|
6
|
+
index = 1
|
7
|
+
when :swap
|
8
|
+
index = 2
|
9
|
+
else
|
10
|
+
raise "Undefined memory type: #{type}"
|
11
|
+
end
|
12
|
+
|
13
|
+
memory = `free -o -m`.split(/\n/)[index].split(" ")
|
14
|
+
|
15
|
+
# memory[1] holds total memory
|
16
|
+
# memory[2] holds used memory
|
17
|
+
((memory[2].to_f / memory[1].to_f) * 100).to_i
|
18
|
+
end
|
19
|
+
end
|
Binary file
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require "pimon/probe/cpu_usage"
|
4
|
+
require "pimon/probe/disk_usage"
|
5
|
+
require "pimon/probe/memory_usage"
|
6
|
+
require "pimon/probe/swap_usage"
|
7
|
+
require "pimon/probe/temperature"
|
8
|
+
|
9
|
+
class StatsCollector
|
10
|
+
def initialize(config, redis)
|
11
|
+
@config = config
|
12
|
+
@redis = redis
|
13
|
+
@probes = [Probe::CpuUsage, Probe::MemoryUsage, Probe::SwapUsage, Probe::DiskUsage, Probe::Temperature]
|
14
|
+
end
|
15
|
+
|
16
|
+
def collect_stats
|
17
|
+
pop_old_stats
|
18
|
+
|
19
|
+
@redis.rpush(@config.queues[:time], Time.now.strftime("%Y-%m-%d %H:%M:%S"))
|
20
|
+
@probes.each do |probe|
|
21
|
+
@redis.rpush(@config.queues[probe.symbol], probe.check)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def last_update
|
26
|
+
time = @redis.lindex(@config.queues[:time], @config.stats[:number_of_stats] - 1)
|
27
|
+
|
28
|
+
DateTime.parse(time) if time
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_stats
|
32
|
+
time = @redis.lrange(@config.queues[:time], 0, -1)
|
33
|
+
|
34
|
+
stats = {
|
35
|
+
:time => { :stats => time.map { |t| (/\d\d:\d\d:\d\d/.match(t))[0] } },
|
36
|
+
:refresh_interval_in_millis => @config.stats[:time_period_in_min] * 60 * 1000
|
37
|
+
}
|
38
|
+
|
39
|
+
@probes.each do |probe|
|
40
|
+
stats[probe.symbol] =
|
41
|
+
{
|
42
|
+
:stats => @redis.lrange(@config.queues[probe.symbol], 0, -1).map(&:to_i),
|
43
|
+
:color => @config.chart[probe.symbol][:color],
|
44
|
+
:unit => probe.unit
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
stats
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def pop_all_queues
|
54
|
+
@redis.lpop(@config.queues[:time])
|
55
|
+
@probes.each { |probe| @redis.lpop(@config.queues[probe.symbol]) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def pop_old_stats
|
59
|
+
queue_size = @redis.llen(@config.queues[:time])
|
60
|
+
|
61
|
+
if queue_size >= @config.stats[:number_of_stats]
|
62
|
+
(queue_size - @config.stats[:number_of_stats] + 1).times { pop_all_queues }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
%html
|
2
|
+
%header
|
3
|
+
%title Pimon
|
4
|
+
%script{ :src => '//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery-1.8.0.min.js' }
|
5
|
+
%script{ :src => '//cdnjs.cloudflare.com/ajax/libs/highcharts/2.3.1/highcharts.js' }
|
6
|
+
%script{ :src => '//lipka.github.com/piecon/piecon.min.js' }
|
7
|
+
%body
|
8
|
+
#chart
|
9
|
+
|
10
|
+
= render(:haml,:'index.js',:locals => { :o => @o })
|
@@ -0,0 +1,111 @@
|
|
1
|
+
:javascript
|
2
|
+
$(function () {
|
3
|
+
var chart,
|
4
|
+
cpu = #{o[:cpu][:stats]},
|
5
|
+
disk = #{o[:disk][:stats]},
|
6
|
+
mem = #{o[:mem][:stats]},
|
7
|
+
swap = #{o[:swap][:stats]},
|
8
|
+
time = #{o[:time][:stats]},
|
9
|
+
temp = #{o[:temp][:stats]},
|
10
|
+
titleStats = [ { 'legend' : 'CPU', 'stat' : cpu[cpu.length - 1], 'color' : '#{o[:cpu][:color]}', 'unit' : '#{o[:cpu][:unit]}' } ,
|
11
|
+
{ 'legend' : 'MEM', 'stat' : mem[mem.length - 1], 'color' : '#{o[:mem][:color]}', 'unit' : '#{o[:mem][:unit]}' } ,
|
12
|
+
{ 'legend' : 'SWAP', 'stat' : swap[swap.length - 1], 'color' : '#{o[:swap][:color]}', 'unit' : '#{o[:swap][:unit]}'},
|
13
|
+
{ 'legend' : 'DISK', 'stat' : disk[disk.length - 1], 'color' : '#{o[:disk][:color]}', 'unit' : '#{o[:disk][:unit]}'},
|
14
|
+
{ 'legend' : 'TEMP', 'stat' : temp[temp.length - 1], 'color' : '#{o[:temp][:color]}', 'unit' : '#{o[:temp][:unit]}'} ],
|
15
|
+
i = 0;
|
16
|
+
|
17
|
+
$(document).ready(function() {
|
18
|
+
chart = new Highcharts.Chart({
|
19
|
+
chart: {
|
20
|
+
renderTo: 'chart',
|
21
|
+
type: 'line',
|
22
|
+
marginRight: 130,
|
23
|
+
marginBottom: 25
|
24
|
+
},
|
25
|
+
title: {
|
26
|
+
text: 'Pimon',
|
27
|
+
x: -20
|
28
|
+
},
|
29
|
+
subtitle: {
|
30
|
+
text: 'My raspberry PI',
|
31
|
+
x: -20
|
32
|
+
},
|
33
|
+
xAxis: {
|
34
|
+
categories: time
|
35
|
+
},
|
36
|
+
yAxis: {
|
37
|
+
title: {
|
38
|
+
text: 'Usage'
|
39
|
+
},
|
40
|
+
plotLines: [{
|
41
|
+
value: 0,
|
42
|
+
width: 1,
|
43
|
+
color: '#808080'
|
44
|
+
}],
|
45
|
+
max: 100,
|
46
|
+
min: 0
|
47
|
+
},
|
48
|
+
tooltip: {
|
49
|
+
formatter: function() {
|
50
|
+
return '<b>'+ this.series.name +'</b><br/>'+
|
51
|
+
this.x +': '+ this.y + (this.series.name === 'temp' ? '#{o[:temp][:unit]}' : '%');
|
52
|
+
}
|
53
|
+
},
|
54
|
+
legend: {
|
55
|
+
layout: 'vertical',
|
56
|
+
align: 'right',
|
57
|
+
verticalAlign: 'top',
|
58
|
+
x: -10,
|
59
|
+
y: 100,
|
60
|
+
borderWidth: 0
|
61
|
+
},
|
62
|
+
series: [{
|
63
|
+
name: 'cpu',
|
64
|
+
color: '#{o[:cpu][:color]}',
|
65
|
+
data: cpu
|
66
|
+
},
|
67
|
+
{
|
68
|
+
name: 'mem',
|
69
|
+
color: '#{o[:mem][:color]}',
|
70
|
+
data: mem
|
71
|
+
},
|
72
|
+
{
|
73
|
+
name: 'swap',
|
74
|
+
color: '#{o[:swap][:color]}',
|
75
|
+
data: swap
|
76
|
+
},
|
77
|
+
{
|
78
|
+
name: 'disk',
|
79
|
+
color: '#{o[:disk][:color]}',
|
80
|
+
data: disk
|
81
|
+
},
|
82
|
+
{
|
83
|
+
name: 'temp',
|
84
|
+
color: '#{o[:temp][:color]}',
|
85
|
+
data: temp
|
86
|
+
}]
|
87
|
+
});
|
88
|
+
});
|
89
|
+
|
90
|
+
function changeFavicon() {
|
91
|
+
Piecon.setOptions({
|
92
|
+
color: titleStats[i]['color'], // Pie chart color
|
93
|
+
background: '#bbb', // Empty pie chart color
|
94
|
+
shadow: '#fff', // Outer ring color
|
95
|
+
fallback: 'force' // Toggles displaying percentage in the title bar (possible values - true, false, 'force')
|
96
|
+
});
|
97
|
+
Piecon.setProgress(titleStats[i]['stat']);
|
98
|
+
|
99
|
+
$('title').text(titleStats[i]['stat'] + titleStats[i]['unit'] + ' ' + titleStats[i]['legend'])
|
100
|
+
i++;
|
101
|
+
if (i >= titleStats.length) {
|
102
|
+
i = 0;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
setInterval(changeFavicon, 3000);
|
107
|
+
changeFavicon();
|
108
|
+
setTimeout(function() {
|
109
|
+
window.location.reload();
|
110
|
+
}, #{@o[:refresh_interval_in_millis]});
|
111
|
+
});
|