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.
@@ -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
@@ -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,11 @@
1
+ require 'pimon/probe/probe'
2
+
3
+ class Probe::CpuUsage < Probe
4
+ def self.check
5
+ 100 - `vmstat 1 2`.split(/\n/)[3].split(" ")[14].to_i
6
+ end
7
+
8
+ def self.symbol
9
+ :cpu
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'pimon/probe/probe'
2
+
3
+ class Probe::DiskUsage < Probe
4
+ def self.check
5
+ `df`.split(/\n/)[1].split(" ")[4].to_i
6
+ end
7
+
8
+ def self.symbol
9
+ :disk
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'pimon/probe/probe'
2
+ require 'pimon/probe/system_memory'
3
+
4
+ class Probe::MemoryUsage < Probe
5
+
6
+ def self.check
7
+ SystemMemory.check(:mem)
8
+ end
9
+
10
+ def self.symbol
11
+ :mem
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class Probe
2
+ def self.check
3
+ raise "Not implemented"
4
+ end
5
+
6
+ def self.symbol
7
+ raise "Not implemented"
8
+ end
9
+
10
+ def self.unit
11
+ '%'
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'pimon/probe/probe'
2
+ require 'pimon/probe/system_memory'
3
+
4
+ class Probe::SwapUsage < Probe
5
+
6
+ def self.check
7
+ SystemMemory.check(:swap)
8
+ end
9
+
10
+ def self.symbol
11
+ :swap
12
+ end
13
+ 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
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+ require 'pimon/probe/probe'
3
+
4
+ class Probe::Temperature < Probe
5
+ def self.check
6
+ `cat /sys/class/thermal/thermal_zone0/temp`[0..1].to_i
7
+ end
8
+
9
+ def self.symbol
10
+ :temp
11
+ end
12
+
13
+ def self.unit
14
+ 'ºC'
15
+ end
16
+ end
@@ -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,3 @@
1
+ module Pimon
2
+ VERSION = '0.1.0'
3
+ 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
+ });