stackeye 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.
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