stackeye 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.fasterer.yml +19 -0
- data/.gitignore +13 -0
- data/.reek.yml +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +119 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/Rakefile +8 -0
- data/app.rb +7 -0
- data/bin/console +15 -0
- data/bin/setup +9 -0
- data/examples/web-ui.png +0 -0
- data/lib/generators/stackeye/install_generator.rb +12 -0
- data/lib/generators/stackeye/templates/install.rb +5 -0
- data/lib/stackeye.rb +7 -0
- data/lib/stackeye/application.rb +32 -0
- data/lib/stackeye/configuration.rb +31 -0
- data/lib/stackeye/helpers/base.rb +56 -0
- data/lib/stackeye/helpers/init.rb +5 -0
- data/lib/stackeye/metrics/all.rb +25 -0
- data/lib/stackeye/metrics/base.rb +113 -0
- data/lib/stackeye/metrics/init.rb +5 -0
- data/lib/stackeye/metrics/mysql.rb +42 -0
- data/lib/stackeye/metrics/server.rb +74 -0
- data/lib/stackeye/public/fonts/feather.eot +0 -0
- data/lib/stackeye/public/fonts/feather.svg +1050 -0
- data/lib/stackeye/public/fonts/feather.ttf +0 -0
- data/lib/stackeye/public/fonts/feather.woff +0 -0
- data/lib/stackeye/public/images/favicon.ico +0 -0
- data/lib/stackeye/public/images/stackeye.png +0 -0
- data/lib/stackeye/public/images/ubuntu.svg +1 -0
- data/lib/stackeye/public/javascripts/3PL.js +15 -0
- data/lib/stackeye/public/javascripts/application.js +242 -0
- data/lib/stackeye/public/stylesheets/3PL.css +5 -0
- data/lib/stackeye/public/stylesheets/application.css +13 -0
- data/lib/stackeye/routes/base.rb +21 -0
- data/lib/stackeye/routes/init.rb +5 -0
- data/lib/stackeye/routes/metrics.rb +17 -0
- data/lib/stackeye/tools/cli.rb +27 -0
- data/lib/stackeye/tools/database.rb +69 -0
- data/lib/stackeye/tools/init.rb +5 -0
- data/lib/stackeye/tools/os.rb +48 -0
- data/lib/stackeye/version.rb +5 -0
- data/lib/stackeye/views/layout.erb +34 -0
- data/lib/stackeye/views/metrics/mysql/_queries.erb +73 -0
- data/lib/stackeye/views/metrics/mysql/index.erb +10 -0
- data/lib/stackeye/views/metrics/server/_cpu.erb +73 -0
- data/lib/stackeye/views/metrics/server/_disk.erb +35 -0
- data/lib/stackeye/views/metrics/server/_memory.erb +73 -0
- data/lib/stackeye/views/metrics/server/_processes.erb +52 -0
- data/lib/stackeye/views/metrics/server/index.erb +23 -0
- data/lib/stackeye/views/shared/_header.erb +20 -0
- data/lib/stackeye/views/shared/_navbar.erb +57 -0
- data/lib/stackeye/views/unsupported.erb +13 -0
- data/stackeye.gemspec +31 -0
- metadata +218 -0
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#!/usr/bin/env ruby
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'stackeye'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require 'pry'
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/examples/web-ui.png
ADDED
Binary file
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
class Stackeye::InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../templates', __FILE__)
|
7
|
+
|
8
|
+
def copy_initializer_file
|
9
|
+
copy_file('install.rb', 'config/initializers/stackeye.rb')
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/lib/stackeye.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
%w[base cookies].each do |filename|
|
4
|
+
require "sinatra/#{filename}"
|
5
|
+
end
|
6
|
+
|
7
|
+
%w[tools metrics helpers routes].each do |dirname|
|
8
|
+
require_relative "#{dirname}/init"
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'logger'
|
12
|
+
|
13
|
+
class Stackeye::Application < Sinatra::Base
|
14
|
+
helpers Sinatra::Cookies
|
15
|
+
|
16
|
+
set :app_file, __FILE__
|
17
|
+
set :bind, '0.0.0.0'
|
18
|
+
|
19
|
+
configure :development do
|
20
|
+
enable :logging, :dump_errors, :raise_errors
|
21
|
+
end
|
22
|
+
|
23
|
+
configure :production do
|
24
|
+
dir = File.expand_path('log')
|
25
|
+
Dir.mkdir(dir) unless File.directory?(dir)
|
26
|
+
file = File.new("#{dir}/stackeye.log", 'a+')
|
27
|
+
file.sync = true
|
28
|
+
|
29
|
+
use ::Rack::CommonLogger, file
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stackeye
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
MAX_DATA ||= 288
|
7
|
+
METRICS ||= %w[server mysql].freeze
|
8
|
+
|
9
|
+
attr_accessor :credentials, :max_data, :metrics
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@max_data = MAX_DATA
|
13
|
+
@metrics = METRICS
|
14
|
+
@credentials = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configuration=(config)
|
24
|
+
@configuration = config
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.configure
|
28
|
+
yield(configuration)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def base_path
|
4
|
+
return unless ENV['RAILS_ENV']
|
5
|
+
|
6
|
+
'/stackeye'
|
7
|
+
end
|
8
|
+
|
9
|
+
def metric_icon_decorator(metric)
|
10
|
+
case metric
|
11
|
+
when 'server' then 'server'
|
12
|
+
else 'database'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def metric_name_decorator(metric)
|
17
|
+
case metric
|
18
|
+
when 'mysql' then 'MySQL'
|
19
|
+
else titleize(metric)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def modulize(str)
|
24
|
+
str.tr('_-', ' ').split(' ').map(&:capitalize).join('')
|
25
|
+
end
|
26
|
+
|
27
|
+
def page?(path)
|
28
|
+
request.path == "#{base_path}#{path}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def refreshing?
|
32
|
+
cookies[:refresh] == '1'
|
33
|
+
end
|
34
|
+
|
35
|
+
def titleize(str)
|
36
|
+
str.tr('_', ' ').capitalize
|
37
|
+
end
|
38
|
+
|
39
|
+
def verified_distro?
|
40
|
+
Stackeye::Tools::Os.linux?
|
41
|
+
end
|
42
|
+
|
43
|
+
def verified_os?
|
44
|
+
cmd = 'lsb_release -ds'
|
45
|
+
Stackeye::Tools::Cli.execute(cmd).strip.include?('Ubuntu')
|
46
|
+
end
|
47
|
+
|
48
|
+
def verified_distro_and_os?
|
49
|
+
verified_distro? && verified_os?
|
50
|
+
end
|
51
|
+
|
52
|
+
def verify_distro_and_os!
|
53
|
+
return if verified_distro_and_os?
|
54
|
+
|
55
|
+
redirect("#{base_path}/unsupported")
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stackeye
|
4
|
+
module Metrics
|
5
|
+
class All
|
6
|
+
|
7
|
+
def initialize; end
|
8
|
+
|
9
|
+
def set
|
10
|
+
Stackeye.configuration.metrics.each do |metric|
|
11
|
+
klass = "Stackeye::Metrics::#{modulize(metric)}"
|
12
|
+
Module.const_get(klass).set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def set
|
18
|
+
klass = new
|
19
|
+
klass.set
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stackeye
|
4
|
+
module Metrics
|
5
|
+
class Base
|
6
|
+
|
7
|
+
MB ||= 1024.0
|
8
|
+
GB ||= MB**2
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@data = { timestamp: Time.now.to_i }
|
12
|
+
end
|
13
|
+
|
14
|
+
def filepath
|
15
|
+
return @filepath if defined?(@filepath)
|
16
|
+
|
17
|
+
@filepath ||= begin
|
18
|
+
path = Stackeye::Tools::Database::DATA_PATH
|
19
|
+
name = self.class.name.split('::').last.downcase
|
20
|
+
|
21
|
+
"#{path}/#{name}.json"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def set
|
26
|
+
generate_data
|
27
|
+
return if @data.empty?
|
28
|
+
|
29
|
+
Stackeye::Tools::Database.set(filepath, @data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get
|
33
|
+
return @get if defined?(@get)
|
34
|
+
|
35
|
+
@get ||= Stackeye::Tools::Database.get(filepath)
|
36
|
+
end
|
37
|
+
|
38
|
+
def pluck(key)
|
39
|
+
@pluck ||= {}
|
40
|
+
return @pluck[key] if @pluck.key?(key)
|
41
|
+
|
42
|
+
@pluck[key] = get.collect { |hash| hash[key] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def mean(key)
|
46
|
+
@mean ||= {}
|
47
|
+
return @mean[key] if @mean.key?(key)
|
48
|
+
|
49
|
+
values = pluck(key)
|
50
|
+
return @mean[key] = 0.0 if values.empty?
|
51
|
+
|
52
|
+
@mean[key] = values.sum / values.length.to_f
|
53
|
+
end
|
54
|
+
|
55
|
+
# rubocop:disable Metrics/AbcSize
|
56
|
+
def median(key)
|
57
|
+
@median ||= {}
|
58
|
+
return @median[key] if @median.key?(key)
|
59
|
+
|
60
|
+
values = pluck(key)
|
61
|
+
return @median[key] = 0.0 if values.empty?
|
62
|
+
|
63
|
+
values_sorted = values.sort
|
64
|
+
values_halved = values.length / 2.0
|
65
|
+
values_halved_sorted = values_sorted[values_halved]
|
66
|
+
return @median[key] = values_halved_sorted unless (values.length % 2).zero?
|
67
|
+
|
68
|
+
@median[key] = (values_sorted[values_halved - 1.0] + values_halved_sorted) / 2.0
|
69
|
+
end
|
70
|
+
# rubocop:enable Metrics/AbcSize
|
71
|
+
|
72
|
+
def mode(key)
|
73
|
+
@mode ||= {}
|
74
|
+
return @mode[key] if @mode.key?(key)
|
75
|
+
|
76
|
+
values = pluck(key)
|
77
|
+
return @mode[key] = 0.0 if values.empty?
|
78
|
+
|
79
|
+
values_distro = values.each_with_object(Hash.new(0)) { |val, hsh| hsh[val] += 1 }
|
80
|
+
values_top_two = values_distro.sort_by { |_, val| -val }.take(2)
|
81
|
+
@mode[key] = values_top_two.first.first
|
82
|
+
end
|
83
|
+
|
84
|
+
def range(key)
|
85
|
+
@range ||= {}
|
86
|
+
return @range[key] if @range.key?(key)
|
87
|
+
|
88
|
+
values = pluck(key)
|
89
|
+
return @range[key] = 0.0 if values.empty?
|
90
|
+
|
91
|
+
values_sorted = values.sort
|
92
|
+
@mode[key] = values_sorted.last - values_sorted.first
|
93
|
+
end
|
94
|
+
|
95
|
+
class << self
|
96
|
+
%i[filepath set get].each do |name|
|
97
|
+
define_method(name) do
|
98
|
+
klass = new
|
99
|
+
klass.send(name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
%i[pluck mean median mode range].each do |name|
|
104
|
+
define_method(name) do |key|
|
105
|
+
klass = new
|
106
|
+
klass.send(name, key)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stackeye
|
4
|
+
module Metrics
|
5
|
+
class Mysql < Stackeye::Metrics::Base
|
6
|
+
|
7
|
+
CREDENTIALS ||= {
|
8
|
+
user: 'root',
|
9
|
+
host: 'localhost',
|
10
|
+
password: nil
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def generate_data
|
14
|
+
generate_stats
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_stats
|
20
|
+
cmd = "mysqladmin -u#{user} -h#{host} -p#{password} status"
|
21
|
+
lines = Stackeye::Tools::Cli.execute(cmd).split("\n")
|
22
|
+
stats = lines.last.strip.split(' ')
|
23
|
+
|
24
|
+
stats.each do |stat|
|
25
|
+
key, val = stat.split(': ')
|
26
|
+
key = key.downcase.tr(' ', '_')
|
27
|
+
key = 'velocity' if key == 'queries_per_second_avg'
|
28
|
+
|
29
|
+
@data[key] = val.to_f.round(2)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
%i[host password user].each do |name|
|
34
|
+
define_method(name) do
|
35
|
+
credentials = Stackeye.configuration.credentials[:mysql]
|
36
|
+
credentials[name] || CREDENTIALS[name]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stackeye
|
4
|
+
module Metrics
|
5
|
+
class Server < Stackeye::Metrics::Base
|
6
|
+
|
7
|
+
def generate_data
|
8
|
+
generate_cpu_loadavg
|
9
|
+
generate_cpu_utilization
|
10
|
+
generate_memory_utilization
|
11
|
+
generate_process_utilization
|
12
|
+
generate_swap_utilization
|
13
|
+
generate_volume_utilization
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def generate_cpu_loadavg
|
19
|
+
{ cpu_load_1m: 1, cpu_load_5m: 2, cpu_load_15m: 3 }.each do |name, col|
|
20
|
+
cmd = "cat /proc/loadavg | awk '{ print $#{col} }'"
|
21
|
+
@data[name] = Stackeye::Tools::Cli.execute(cmd).strip.to_f
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_cpu_utilization
|
26
|
+
cmd = "ps -Ao %cpu | awk '{ s += $1 } END { print s }'"
|
27
|
+
@data[:cpu_utilization] = Stackeye::Tools::Cli.execute(cmd).strip.to_f
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_process_utilization
|
31
|
+
{ cpu: 'pcpu', memory: 'pmem' }.each do |label, sort|
|
32
|
+
key = "#{label}_processes".to_sym
|
33
|
+
cmd = "ps -Ao user,uid,comm,pid,pcpu,pmem --sort=-#{sort} | head -n 11"
|
34
|
+
processes = Stackeye::Tools::Cli.execute(cmd)
|
35
|
+
|
36
|
+
@data[key] = []
|
37
|
+
processes.split("\n").each_with_index do |process, i|
|
38
|
+
next if i.zero?
|
39
|
+
|
40
|
+
@data[key] << process.strip.gsub(/\s+/, ' ').split(' ')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_memory_utilization
|
46
|
+
{ memory_free: 4, memory_total: 2, memory_used: 3 }.each do |name, col|
|
47
|
+
cmd = "/usr/bin/free | head -n 2 | tail -n 1 | awk '{ print $#{col} }'"
|
48
|
+
memory = Stackeye::Tools::Cli.execute(cmd).strip.to_f
|
49
|
+
|
50
|
+
@data[name] = (memory / GB).round(2)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_swap_utilization
|
55
|
+
{ swap_free: 4, swap_total: 2, swap_used: 3 }.each do |name, col|
|
56
|
+
cmd = "/usr/bin/free | tail -n 1 | awk '{ print $#{col} }'"
|
57
|
+
swap = Stackeye::Tools::Cli.execute(cmd).strip.to_f
|
58
|
+
|
59
|
+
@data[name] = (swap / GB).round(2)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def generate_volume_utilization
|
64
|
+
{ disk_free: 4, disk_total: 2, disk_used: 3 }.each do |name, col|
|
65
|
+
cmd = "/bin/df --total | tail -n 1 | awk '{ print $#{col} }'"
|
66
|
+
volume = Stackeye::Tools::Cli.execute(cmd).strip.to_f
|
67
|
+
|
68
|
+
@data[name] = (volume / GB).round(2)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|