greenhat 0.5.1 → 0.6.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/greenhat/archive.rb +2 -0
  3. data/lib/greenhat/cli.rb +12 -2
  4. data/lib/greenhat/entrypoint.rb +36 -33
  5. data/lib/greenhat/logbot.rb +1 -1
  6. data/lib/greenhat/paper/flag_helper.rb +18 -0
  7. data/lib/greenhat/paper/paper_helper.rb +118 -0
  8. data/lib/greenhat/paper.rb +34 -0
  9. data/lib/greenhat/reports/builder.rb +98 -0
  10. data/lib/greenhat/reports/helpers.rb +101 -0
  11. data/lib/greenhat/reports/internal_methods.rb +156 -0
  12. data/lib/greenhat/reports/reports/errors.rb +49 -0
  13. data/lib/greenhat/reports/reports/faststats.rb +42 -0
  14. data/lib/greenhat/reports/reports/full.rb +143 -0
  15. data/lib/greenhat/reports/runner.rb +58 -0
  16. data/lib/greenhat/reports/shared.rb +37 -0
  17. data/lib/greenhat/reports/shell_helper.rb +34 -0
  18. data/lib/greenhat/reports.rb +79 -0
  19. data/lib/greenhat/settings.rb +6 -1
  20. data/lib/greenhat/shell/args.rb +9 -9
  21. data/lib/greenhat/shell/color_string.rb +1 -1
  22. data/lib/greenhat/shell/faststats.rb +24 -5
  23. data/lib/greenhat/shell/field_helper.rb +1 -1
  24. data/lib/greenhat/shell/filter_help.rb +36 -189
  25. data/lib/greenhat/shell/log.rb +18 -14
  26. data/lib/greenhat/shell/markdown.rb +355 -352
  27. data/lib/greenhat/shell/process.rb +11 -5
  28. data/lib/greenhat/shell/query.rb +183 -27
  29. data/lib/greenhat/shell/report.rb +415 -412
  30. data/lib/greenhat/shell/reports.rb +41 -0
  31. data/lib/greenhat/shell/shell_helper.rb +92 -34
  32. data/lib/greenhat/shell.rb +13 -2
  33. data/lib/greenhat/thing/file_types.rb +14 -0
  34. data/lib/greenhat/thing/formatters/clean_raw.rb +1 -1
  35. data/lib/greenhat/thing/formatters/runner_log.rb +70 -0
  36. data/lib/greenhat/thing/formatters/time_json.rb +12 -1
  37. data/lib/greenhat/thing/kind.rb +1 -1
  38. data/lib/greenhat/version.rb +1 -1
  39. data/lib/greenhat.rb +6 -8
  40. metadata +31 -4
  41. data/lib/greenhat/pry_helpers.rb +0 -51
  42. data/lib/greenhat/thing/super_log.rb +0 -1
@@ -0,0 +1,49 @@
1
+ archive_header
2
+
3
+ help do
4
+ header 'Error Reports'
5
+ puts 'This will collect errors from the exceptions, production, application, api, workhorse, and sidekiq logs'
6
+ puts 'Without arguments it will count and unique the error messages'
7
+ br
8
+ puts indent("#{'--verbose'.pastel(:green)} Print all of the error messages")
9
+ puts indent("#{'--filter'.pastel(:green)} Filter Error messages")
10
+ puts indent('Ex: --filter=exception,production'.pastel(:cyan), 4)
11
+ puts indent('Ex: --filter!=api'.pastel(:cyan), 4)
12
+ end
13
+
14
+ entries = [
15
+ { header: 'Exceptions', base: 'gitlab-rails/exceptions_json.log', stats: 'exception.message,exception.class' },
16
+ { header: 'Production', base: 'gitlab-rails/production_json.log --status=500 ',
17
+ stats: 'exception.message,exception.class' },
18
+ {
19
+ header: 'Application',
20
+ base: 'gitlab-rails/application_json.log --message!="Cannot obtain an exclusive lease" --severity=error',
21
+ stats: 'message'
22
+ },
23
+ { header: 'Workhorse', base: 'gitlab-workhorse/current --level=error', stats: 'error' },
24
+ { header: 'Sidekiq', base: 'sidekiq/current --severity=error', stats: 'message' },
25
+ { header: 'API', base: 'gitlab-rails/api_json.log --severity=error',
26
+ stats: 'exception.class,exception.message,status' }
27
+ ]
28
+
29
+ # Filter Logic
30
+ filters = args_select(:filter)
31
+ unless filters.empty?
32
+ entries.select! do |entry|
33
+ filters.any? do |filter|
34
+ !filter.bang if filter.value.split(',').any? { |x| entry.header.downcase.include? x }
35
+ end
36
+ end
37
+ end
38
+
39
+ # Return output
40
+ entries.each do |entry|
41
+ query_string = flags.verbose ? entry.base : entry.base + " --stats=#{entry.stats}"
42
+ query(query_string, false) do |results|
43
+ next if results.map(&:values).map(&:first).sum.zero?
44
+
45
+ header entry.header
46
+
47
+ show results
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ archive_header
2
+
3
+ help do
4
+ header 'FastStats'
5
+ puts 'Run against all the things'
6
+ br
7
+ puts indent("#{'--filter'.pastel(:green)} Filter for specific files")
8
+ puts indent('Ex: --filter=gitaly,production'.pastel(:cyan), 4)
9
+ puts indent('Ex: --filter!=api'.pastel(:cyan), 4)
10
+ br
11
+ puts indent("#{'--top'.pastel(:green)} switch to `fast-stats top [filename]`")
12
+ puts indent("#{'--errors'.pastel(:green)} switch to `fast-stats errors [filename]`")
13
+ br
14
+ puts indent("#{'--all'.pastel(:green)} to attempt to faststats everything")
15
+ br
16
+ puts 'Combine all the args: `faststats --filter=sidekiq --top --errors`'
17
+ end
18
+
19
+ cmds = []
20
+ cmds.push 'top' if flags.top
21
+ cmds.push 'errors' if flags.errors
22
+ cmds.push nil if cmds.empty? # Assume Default
23
+
24
+ filters = args_select(:filter).flat_map { |x| x.value.split(',') }
25
+
26
+ if filters.empty?
27
+ # Add Defaults
28
+ filters = [
29
+ 'gitaly/current',
30
+ 'gitlab-rails/api_json.log',
31
+ 'gitlab-rails/production_json.log',
32
+ 'sidekiq/current'
33
+ ]
34
+ end
35
+
36
+ filters = ['*'] if flags.all
37
+
38
+ filters.each do |filter|
39
+ cmds.each do |cmd|
40
+ faststats filter, cmd
41
+ end
42
+ end
@@ -0,0 +1,143 @@
1
+ quiet!
2
+ archive_header
3
+ header 'OS'
4
+
5
+ cat 'hostname' do |data|
6
+ indent "#{'Hostname:'.pastel(:cyan)} #{data.join}"
7
+ end
8
+
9
+ info 'etc/os-release' do |data|
10
+ ident = "[#{data.ID}] ".pastel(:bright_black)
11
+ pretty_name = data.PRETTY_NAME
12
+ " #{'Distro'.pastel(:cyan)} #{ident} #{pretty_name}"
13
+ end
14
+
15
+ cat 'uname' do |data|
16
+ value, build = data.first.split[2].split('-')
17
+ build = "(#{build})".pastel(:bright_black)
18
+ " #{'Kernel'.pastel(:cyan)} #{value} #{build}"
19
+ end
20
+
21
+ cat 'uptime' do |data|
22
+ init = data.join.split(', load average').first.strip.split('up ', 2).last
23
+ " #{'Uptime'.pastel(:cyan)} #{init}"
24
+ end
25
+
26
+ # Load Average
27
+ info 'lscpu' do |data|
28
+ uptime = archive.thing? 'uptime'
29
+ return unless uptime
30
+
31
+ cpu_count = data['CPU(s)'].to_i
32
+ intervals = uptime.data.first.split('load average: ', 2).last.split(', ').map(&:to_f)
33
+
34
+ intervals_text = intervals.map do |interval|
35
+ value = percent(interval, cpu_count)
36
+ color = value > 100 ? :red : :green
37
+ [
38
+ interval,
39
+ ' (',
40
+ "#{value}%".pastel(color),
41
+ ')'
42
+ ].join
43
+ end
44
+
45
+ " #{'LoadAvg'.pastel(:cyan)} #{"[CPU #{cpu_count}] ".pastel(:bright_white)} #{intervals_text.join(', ')}"
46
+ end
47
+
48
+ br
49
+ header 'Memory'
50
+
51
+ info('meminfo') do |data|
52
+ total = human_size_to_number(data['MemTotal'])
53
+ free = human_size_to_number(data['MemFree'])
54
+ used = percent((total - free), total)
55
+
56
+ [
57
+ ' ',
58
+ ljust('Usage', 13, :yellow),
59
+ '['.pastel(:bright_black),
60
+ '='.pastel(:green) * (used / 2),
61
+ ' ' * (50 - (used / 2)),
62
+ ']'.pastel(:bright_black),
63
+ " #{100 - percent(free, total)}%".pastel(:green) # Inverse
64
+ ].join
65
+ end
66
+
67
+ info('free_m', false) do |data|
68
+ free = data.first
69
+
70
+ unless free.total.blank?
71
+ size = number_to_human_size(free.total.to_i * (1024**2))
72
+ puts " #{ljust('Total', 12, :cyan)} #{size}"
73
+ end
74
+
75
+ unless free.used.blank?
76
+ size = number_to_human_size(free.used.to_i * (1024**2))
77
+
78
+ puts " #{ljust('Used', 12, :yellow)} #{size}"
79
+ end
80
+
81
+ unless free.free.blank?
82
+ size = number_to_human_size(free.free.to_i * (1024**2))
83
+ puts " #{ljust('Free', 12, :blue)} #{size}"
84
+ end
85
+
86
+ unless free.available.blank?
87
+ size = number_to_human_size(free.available.to_i * (1024**2))
88
+ puts " #{ljust('Available', 12, :green)} #{size}"
89
+ end
90
+
91
+ br
92
+ data.map { |x| GreenHat::Memory.memory_row x }.each do |row|
93
+ puts indent(row)
94
+ end
95
+ end
96
+
97
+ br
98
+ header 'GitLab'
99
+ info 'gitlab/version-manifest.json' do |data|
100
+ indent("#{'Version'.pastel(:cyan)}: #{data.build_version}")
101
+ end
102
+
103
+ info 'gitlab_status' do
104
+ indent(GreenHat::GitLab.services(archive, 3), 3)
105
+ end
106
+
107
+ br
108
+ puts indent('Errors'.pastel(:cyan))
109
+
110
+ query 'gitlab-rails/production_json.log --status=500' do |data|
111
+ color = data.count.zero? ? :green : :red
112
+ indent("#{ljust('Production:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
113
+ end
114
+
115
+ query 'gitlab-rails/application_json.log --message!="Cannot obtain an exclusive lease" --severity=error' do |data|
116
+ color = data.count.zero? ? :green : :red
117
+ indent("#{ljust('Application:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
118
+ end
119
+
120
+ query 'sidekiq/current --severity=error' do |data|
121
+ color = data.count.zero? ? :green : :red
122
+ indent("#{ljust('Sidekiq:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
123
+ end
124
+
125
+ query 'gitlab-rails/api_json.log --status=500' do |data|
126
+ color = data.count.zero? ? :green : :red
127
+ indent("#{ljust('API:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
128
+ end
129
+
130
+ query 'gitaly/current --level=error' do |data|
131
+ color = data.count.zero? ? :green : :red
132
+ indent("#{ljust('Gitaly:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
133
+ end
134
+
135
+ query 'gitlab-workhorse/current --level=error' do |data|
136
+ color = data.count.zero? ? :green : :red
137
+ indent("#{ljust('Workhorse:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
138
+ end
139
+
140
+ query 'gitlab-rails/exceptions_json.log' do |data|
141
+ color = data.count.zero? ? :green : :red
142
+ indent("#{ljust('Exceptions:', 14, :magenta)} #{data.count.to_s.pastel(color)}", 4)
143
+ end
@@ -0,0 +1,58 @@
1
+ # GreenHat Namespace
2
+ module GreenHat
3
+ # Reports NameSpace
4
+ module Reports
5
+ # This guy actually runs the reports
6
+ class Runner
7
+ include ActionView::Helpers::DateHelper
8
+ include ActionView::Helpers::NumberHelper
9
+
10
+ include InternalMethods
11
+ include Shared
12
+ attr_accessor :store, :archive, :flags, :args, :output
13
+
14
+ # Make Pry Easier
15
+ def inspect
16
+ "#<Runner archive: '#{archive}'>"
17
+ end
18
+
19
+ def initialize(archive:, store:, flags:, args:)
20
+ self.store = store
21
+ self.archive = archive
22
+ self.flags = flags
23
+ self.args = args
24
+ self.output = []
25
+
26
+ # Don't render paper new lines
27
+ # flags[:skip_new_line] = true
28
+ end
29
+
30
+ # Main Entrypoint for Triggering Reports Run
31
+ def run!
32
+ if flags.help
33
+ # Check for Help Flag
34
+ help_flag = store.find { |x| x.first == :help }
35
+ help_flag ? run_entry(help_flag) : puts('No Help :(')
36
+ return true
37
+ end
38
+
39
+ store.each do |entry|
40
+ next if entry.first == :help # Skip if no flags
41
+
42
+ run_entry(entry)
43
+ end
44
+
45
+ puts
46
+ rescue StandardError => e
47
+ puts e.message
48
+ puts e.backtrace
49
+ end
50
+
51
+ # Helper to evaluate Entry
52
+ def run_entry(entry)
53
+ method = entry[0]
54
+ send(method, *entry[1..])
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ # GreenHat Namespace
2
+ module GreenHat
3
+ # Reports Parent Class
4
+ module Reports
5
+ # Method Helper Module
6
+ module Shared
7
+ # Indentation Helper
8
+ def indent(text, indent_value = 2)
9
+ if indent_value.zero?
10
+ text
11
+ else
12
+ "#{' ' * indent_value}#{text}"
13
+ end
14
+ end
15
+
16
+ def percent(value, total)
17
+ ((value / total.to_f) * 100).round
18
+ end
19
+
20
+ # Justify Text Helper / With Color!
21
+ def ljust(text, value = 14, color = nil)
22
+ color ? text.ljust(value).pastel(color) : text.ljust(value)
23
+ end
24
+
25
+ # Number Helper
26
+ def human_size_to_number(value)
27
+ GreenHat::ShellHelper.human_size_to_number value
28
+ end
29
+
30
+ # Helper to filter arg selection
31
+ def args_select(field)
32
+ args.select { |x| x.field == field.to_sym }
33
+ end
34
+ # -=-=-=-=-=-
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ module GreenHat
2
+ module ShellHelper
3
+ # Log Helpers
4
+ module Reports
5
+ # Make Running more consistent
6
+ def self.run(file:, args:, flags:)
7
+ report = GreenHat::Reports::Builder.new(file: file, args: args, flags: flags)
8
+ output = Archive.all.map do |archive|
9
+ runner = GreenHat::Reports::Runner.new(
10
+ archive: archive,
11
+ store: report.store.clone,
12
+ flags: flags,
13
+ args: args
14
+ )
15
+
16
+ runner.run!
17
+
18
+ runner
19
+ end
20
+
21
+ # Check for Pagination
22
+ flags[:page] = ENV['GREENHAT_PAGE'] == 'true' if ENV['GREENHAT_PAGE']
23
+
24
+ ShellHelper.show(output.flat_map(&:output), flags)
25
+ end
26
+
27
+ # Collect everything from Built-in and local reports
28
+ def self.list
29
+ path = "#{File.dirname(__FILE__)}/reports"
30
+ Dir["#{path}/*.rb"] + Dir["#{GreenHat::Settings.reports_dir}/*.rb"]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ require 'greenhat'
2
+ require 'greenhat/reports/internal_methods'
3
+ require 'greenhat/reports/helpers'
4
+ require 'greenhat/reports/runner'
5
+
6
+ GreenHat::Cli.quiet!
7
+
8
+ # GreenHat Namespace
9
+ module GreenHat
10
+ # Reports Parent Class
11
+ module Reports
12
+ # Method Storing
13
+ def self.store
14
+ @store ||= []
15
+
16
+ @store
17
+ end
18
+
19
+ def self.output
20
+ @output ||= []
21
+ end
22
+
23
+ # Adding Methods
24
+ def self.add(*params)
25
+ store.push params
26
+ end
27
+
28
+ # Main Entrypoint for Triggering Reports Run
29
+ def self.run!
30
+ _list, flags, args = Args.parse(ARGV)
31
+
32
+ output = Archive.all.map do |archive|
33
+ runner = GreenHat::Reports::Runner.new(
34
+ archive: archive,
35
+ store: store.clone,
36
+ flags: flags,
37
+ args: args
38
+ )
39
+
40
+ runner.run!
41
+
42
+ break if flags.help # Only Run Once
43
+
44
+ runner
45
+ end
46
+
47
+ # Check for Pagination
48
+ flags[:page] = ENV['GREENHAT_PAGE'] == 'true' if ENV['GREENHAT_PAGE']
49
+
50
+ ShellHelper.show(output.flat_map(&:output), flags)
51
+ end
52
+
53
+ def self.archive_run(archive)
54
+ store.each do |entry|
55
+ method = entry[0]
56
+ # Skip anything for Shim'd Puts
57
+ if method == :puts
58
+ send(method, *entry[1..])
59
+ else
60
+ send(method, archive, *entry[1..])
61
+ end
62
+ end
63
+ rescue StandardError => e
64
+ puts e.message
65
+ puts e.backtrace
66
+ end
67
+ # ==
68
+ end
69
+ end
70
+
71
+ at_exit do
72
+ Dir.mktmpdir('greenhat-sauce') do |tmp|
73
+ $TMP = tmp
74
+ $STDOUT_CLONE = $stdout.clone
75
+ ENV['GREENHAT_REPORT'] = 'true' # Flag to allow skipping of different entypoints
76
+ GreenHat::Entrypoint.start(ARGV, false, false)
77
+ GreenHat::Reports.run!
78
+ end
79
+ end
@@ -83,6 +83,7 @@ module GreenHat
83
83
 
84
84
  def self.start
85
85
  Dir.mkdir dir unless Dir.exist? dir
86
+ Dir.mkdir reports_dir unless Dir.exist? reports_dir
86
87
 
87
88
  # Load User Settings
88
89
  settings_load
@@ -92,7 +93,11 @@ module GreenHat
92
93
  end
93
94
 
94
95
  def self.dir
95
- "#{ENV['HOME']}/.greenhat"
96
+ "#{Dir.home}/.greenhat"
97
+ end
98
+
99
+ def self.reports_dir
100
+ "#{dir}/reports"
96
101
  end
97
102
 
98
103
  # ----------------------------------
@@ -70,11 +70,18 @@ module GreenHat
70
70
  end
71
71
  end
72
72
 
73
+ # Arguments that Accept multiple options / Comma Delimted
74
+ def self.arg_special_split
75
+ %i[
76
+ slice except uniq pluck sort archive stats exists transform
77
+ ]
78
+ end
79
+
73
80
  # Flags Anything that isn't sent as a key/filter
74
81
  def self.arg_to_flag_list
75
82
  %i[
76
- archive end except exists json limit pluck reverse round slice sort start
77
- stats truncate uniq page time_zone table_style
83
+ archive end except exists json limit pluck reverse round slice sort start total
84
+ stats truncate uniq page time_zone table_style percentile interval percentile transform
78
85
  ]
79
86
  end
80
87
 
@@ -136,13 +143,6 @@ module GreenHat
136
143
  end
137
144
  end
138
145
 
139
- # Arguments that Accept multiple options / Comma Delimted
140
- def self.arg_special_split
141
- %i[
142
- slice except uniq pluck sort archive stats exists
143
- ]
144
- end
145
-
146
146
  # Arg Defaults
147
147
  def self.flag_arg_defaults(field)
148
148
  case field
@@ -3,7 +3,7 @@ module GreenHat
3
3
  # Helper to colorize and make outtput easier to read
4
4
  module StringColor
5
5
  def self.do(key, entry)
6
- LogBot.debug('Unknown Format', entry.class) if ENV['DEBUG'] && !entry.instance_of?(String)
6
+ LogBot.debug('Unknown Format', entry.class) if ENV.fetch('DEBUG', nil) && !entry.instance_of?(String)
7
7
 
8
8
  # Other Helpful colorizers
9
9
  if pastel?(key)
@@ -100,6 +100,13 @@ module GreenHat
100
100
  puts "- #{log.name.pastel(:yellow)}"
101
101
  end
102
102
  end
103
+
104
+ return unless all
105
+
106
+ puts "\n#{'Other / Unknown'.pastel(:red)}"
107
+ (Thing.all - files).each do |file|
108
+ puts "- #{file.name.pastel(:yellow)}"
109
+ end
103
110
  end
104
111
 
105
112
  def self.ls(args = [])
@@ -117,15 +124,23 @@ module GreenHat
117
124
 
118
125
  LogBot.debug('FastStats CMD', cmd) if ENV['DEBUG']
119
126
 
127
+ # binding.pry
128
+
129
+ # Ignore Unknown Errors
120
130
  results = ShellHelper.file_process(files) do |file|
131
+ output = `fast-stats #{cmd} #{file.file} 2>&1`
132
+ result = $CHILD_STATUS.success?
133
+
134
+ next unless result
135
+
121
136
  [
122
137
  file.friendly_name,
123
- `fast-stats #{cmd} #{file.file}`.split("\n"),
138
+ output.split("\n"),
124
139
  "\n"
125
140
  ]
126
141
  end
127
142
 
128
- ShellHelper.show(results.flatten, flags)
143
+ ShellHelper.show(results.compact.flatten, flags)
129
144
  end
130
145
 
131
146
  # ========================================================================
@@ -219,11 +234,15 @@ module GreenHat
219
234
  end
220
235
  end.join(' ')
221
236
 
222
- # Prepare Log List
223
- file_list = ShellHelper.prepare_list(file_list, ShellHelper::Faststats.things, flags)
237
+ # Prepare Log List / Allow attempting of parsing everything
238
+ file_list = if file_list == ['*']
239
+ Thing.all.map(&:name)
240
+ else
241
+ ShellHelper.prepare_list(file_list, ShellHelper::Faststats.things)
242
+ end
224
243
 
225
244
  # Convert to Things
226
- files = ShellHelper.find_things(file_list)
245
+ files = ShellHelper.find_things(file_list, fuzzy_file_match: true)
227
246
 
228
247
  [files, flags, cmd]
229
248
  end
@@ -34,7 +34,7 @@ module GreenHat
34
34
  %w[
35
35
  archive case combine end exact except exists json limit or page pluck
36
36
  raw reverse round slice sort start stats table_style text time_zone
37
- total truncate uniq
37
+ total truncate uniq percentile transform
38
38
  ]
39
39
  end
40
40