stackeye 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.fasterer.yml +19 -0
  4. data/.gitignore +13 -0
  5. data/.reek.yml +20 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +18 -0
  8. data/.travis.yml +5 -0
  9. data/CHANGELOG.md +11 -0
  10. data/CODE_OF_CONDUCT.md +74 -0
  11. data/Gemfile +8 -0
  12. data/Gemfile.lock +119 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +92 -0
  15. data/Rakefile +8 -0
  16. data/app.rb +7 -0
  17. data/bin/console +15 -0
  18. data/bin/setup +9 -0
  19. data/examples/web-ui.png +0 -0
  20. data/lib/generators/stackeye/install_generator.rb +12 -0
  21. data/lib/generators/stackeye/templates/install.rb +5 -0
  22. data/lib/stackeye.rb +7 -0
  23. data/lib/stackeye/application.rb +32 -0
  24. data/lib/stackeye/configuration.rb +31 -0
  25. data/lib/stackeye/helpers/base.rb +56 -0
  26. data/lib/stackeye/helpers/init.rb +5 -0
  27. data/lib/stackeye/metrics/all.rb +25 -0
  28. data/lib/stackeye/metrics/base.rb +113 -0
  29. data/lib/stackeye/metrics/init.rb +5 -0
  30. data/lib/stackeye/metrics/mysql.rb +42 -0
  31. data/lib/stackeye/metrics/server.rb +74 -0
  32. data/lib/stackeye/public/fonts/feather.eot +0 -0
  33. data/lib/stackeye/public/fonts/feather.svg +1050 -0
  34. data/lib/stackeye/public/fonts/feather.ttf +0 -0
  35. data/lib/stackeye/public/fonts/feather.woff +0 -0
  36. data/lib/stackeye/public/images/favicon.ico +0 -0
  37. data/lib/stackeye/public/images/stackeye.png +0 -0
  38. data/lib/stackeye/public/images/ubuntu.svg +1 -0
  39. data/lib/stackeye/public/javascripts/3PL.js +15 -0
  40. data/lib/stackeye/public/javascripts/application.js +242 -0
  41. data/lib/stackeye/public/stylesheets/3PL.css +5 -0
  42. data/lib/stackeye/public/stylesheets/application.css +13 -0
  43. data/lib/stackeye/routes/base.rb +21 -0
  44. data/lib/stackeye/routes/init.rb +5 -0
  45. data/lib/stackeye/routes/metrics.rb +17 -0
  46. data/lib/stackeye/tools/cli.rb +27 -0
  47. data/lib/stackeye/tools/database.rb +69 -0
  48. data/lib/stackeye/tools/init.rb +5 -0
  49. data/lib/stackeye/tools/os.rb +48 -0
  50. data/lib/stackeye/version.rb +5 -0
  51. data/lib/stackeye/views/layout.erb +34 -0
  52. data/lib/stackeye/views/metrics/mysql/_queries.erb +73 -0
  53. data/lib/stackeye/views/metrics/mysql/index.erb +10 -0
  54. data/lib/stackeye/views/metrics/server/_cpu.erb +73 -0
  55. data/lib/stackeye/views/metrics/server/_disk.erb +35 -0
  56. data/lib/stackeye/views/metrics/server/_memory.erb +73 -0
  57. data/lib/stackeye/views/metrics/server/_processes.erb +52 -0
  58. data/lib/stackeye/views/metrics/server/index.erb +23 -0
  59. data/lib/stackeye/views/shared/_header.erb +20 -0
  60. data/lib/stackeye/views/shared/_navbar.erb +57 -0
  61. data/lib/stackeye/views/unsupported.erb +13 -0
  62. data/stackeye.gemspec +31 -0
  63. metadata +218 -0
@@ -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__)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ #!/usr/bin/env bash
3
+ set -euo pipefail
4
+ IFS=$'\n\t'
5
+ set -vx
6
+
7
+ bundle install
8
+
9
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,5 @@
1
+ Stackeye.configure do |config|
2
+ config.max_data = 288
3
+ config.metrics = %w[server mysql]
4
+ config.credentials = {}
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[version configuration application].each do |filename|
4
+ require "stackeye/#{filename}"
5
+ end
6
+
7
+ require 'generators/stackeye/install_generator'
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[base].each do |filename|
4
+ require_relative filename
5
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[base server mysql all].each do |filename|
4
+ require_relative filename
5
+ 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