greenhat 0.5.0 → 0.6.2

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/greenhat/accessors/disk.rb +1 -1
  3. data/lib/greenhat/archive.rb +2 -0
  4. data/lib/greenhat/cli.rb +12 -2
  5. data/lib/greenhat/entrypoint.rb +36 -33
  6. data/lib/greenhat/host.rb +1 -1
  7. data/lib/greenhat/logbot.rb +1 -1
  8. data/lib/greenhat/paper/flag_helper.rb +18 -0
  9. data/lib/greenhat/paper/paper_helper.rb +118 -0
  10. data/lib/greenhat/paper.rb +34 -0
  11. data/lib/greenhat/reports/builder.rb +98 -0
  12. data/lib/greenhat/reports/helpers.rb +101 -0
  13. data/lib/greenhat/reports/internal_methods.rb +156 -0
  14. data/lib/greenhat/reports/reports/errors.rb +51 -0
  15. data/lib/greenhat/reports/reports/faststats.rb +42 -0
  16. data/lib/greenhat/reports/reports/full.rb +143 -0
  17. data/lib/greenhat/reports/runner.rb +58 -0
  18. data/lib/greenhat/reports/shared.rb +37 -0
  19. data/lib/greenhat/reports/shell_helper.rb +34 -0
  20. data/lib/greenhat/reports.rb +79 -0
  21. data/lib/greenhat/settings.rb +6 -1
  22. data/lib/greenhat/shell/args.rb +9 -9
  23. data/lib/greenhat/shell/color_string.rb +1 -1
  24. data/lib/greenhat/shell/faststats.rb +24 -5
  25. data/lib/greenhat/shell/field_helper.rb +1 -1
  26. data/lib/greenhat/shell/filter_help.rb +36 -189
  27. data/lib/greenhat/shell/log.rb +28 -16
  28. data/lib/greenhat/shell/markdown.rb +355 -352
  29. data/lib/greenhat/shell/process.rb +11 -5
  30. data/lib/greenhat/shell/query.rb +184 -28
  31. data/lib/greenhat/shell/report.rb +415 -412
  32. data/lib/greenhat/shell/reports.rb +41 -0
  33. data/lib/greenhat/shell/shell_helper.rb +172 -117
  34. data/lib/greenhat/shell.rb +13 -2
  35. data/lib/greenhat/thing/file_types.rb +38 -2
  36. data/lib/greenhat/thing/formatters/clean_raw.rb +1 -1
  37. data/lib/greenhat/thing/formatters/exporters.rb +48 -0
  38. data/lib/greenhat/thing/formatters/identify_db.rb +32 -0
  39. data/lib/greenhat/thing/formatters/runner_log.rb +70 -0
  40. data/lib/greenhat/thing/formatters/table.rb +15 -1
  41. data/lib/greenhat/thing/formatters/time_json.rb +12 -1
  42. data/lib/greenhat/thing/kind.rb +1 -1
  43. data/lib/greenhat/thing.rb +1 -0
  44. data/lib/greenhat/version.rb +1 -1
  45. data/lib/greenhat.rb +6 -8
  46. metadata +33 -4
  47. data/lib/greenhat/pry_helpers.rb +0 -51
  48. data/lib/greenhat/thing/super_log.rb +0 -1
@@ -0,0 +1,156 @@
1
+ # GreenHat Namespace
2
+ module GreenHat
3
+ # Reports Parent Class
4
+ module Reports
5
+ # Method Helper Module
6
+ module InternalMethods
7
+ # Archive Header Printer
8
+ def archive_header(color = :blue)
9
+ puts archive.friendly_name.pastel(color)
10
+ end
11
+
12
+ # Header Printer
13
+ def header(text, color = :bright_yellow, bold = :bold)
14
+ puts text.pastel(color, bold)
15
+ end
16
+
17
+ # Raw Print
18
+ def cat(name, show_output, block)
19
+ thing = archive.thing? name
20
+ return unless thing
21
+
22
+ output = instance_exec(thing.raw_full, &block)
23
+ show output if show_output
24
+ end
25
+
26
+ # Parsed Print
27
+ def info(name, show_output, block)
28
+ thing = archive.thing? name
29
+ return unless thing
30
+
31
+ output = instance_exec(thing.data, &block)
32
+ show output if show_output
33
+ end
34
+
35
+ # Help
36
+ def help(block)
37
+ instance_exec(&block)
38
+ end
39
+
40
+ # Log Query
41
+ def query(query, show_output, block)
42
+ files, query_flags, query_args = GreenHat::Args.parse(Shellwords.split(query))
43
+ query_flags[:archive] = [archive.name] # Limit query to archive
44
+ query_flags[:combine] = true
45
+
46
+ # Default to everything
47
+ files = archive.things if files.empty?
48
+
49
+ results = Query.start(files, query_flags, query_args)
50
+
51
+ # Print and Exit of No Block / Show
52
+ return show(results) if block.nil? && show_output
53
+
54
+ output = instance_exec(results, &block)
55
+ show output if show_output
56
+ end
57
+
58
+ # Log Query / Assume Format
59
+ def query_format(query, show_output, block)
60
+ files, query_flags, query_args = GreenHat::Args.parse(Shellwords.split(query))
61
+ query_flags[:archive] = [archive.name] # Limit query to archive
62
+ query_flags[:combine] = true
63
+
64
+ # Default to everything
65
+ query_flags = archive.things if files.empty?
66
+
67
+ # Filter
68
+ results = GreenHat::Query.start(files, query_flags, query_args)
69
+
70
+ # Print and Exit of No Block / Show
71
+ return show(results) if block.nil? && show_output
72
+
73
+ block_output = instance_exec(results, &block)
74
+ show block_output if show_output
75
+ end
76
+
77
+ def faststats(raw, subcmd)
78
+ return true unless TTY::Which.exist? 'fast-stats'
79
+
80
+ files, _flags, cmd = ShellHelper::Faststats.parse(Shellwords.split(raw))
81
+
82
+ results = ShellHelper.file_process(files) do |file|
83
+ output = `fast-stats #{subcmd} #{cmd} #{file.file} 2>&1`
84
+ result = $CHILD_STATUS.success?
85
+
86
+ next unless result
87
+
88
+ [
89
+ file.name.pastel(:green),
90
+ output.split("\n"),
91
+ "\n"
92
+ ]
93
+ end
94
+
95
+ show results.compact.flatten
96
+ end
97
+
98
+ # TODO
99
+ # results = GreenHat::ShellHelper::Faststats.run(thing, 'top') # JSON FORMAT
100
+
101
+ # Paper Render Helper
102
+ def show(value)
103
+ if value.instance_of?(Array)
104
+ value.each do |x|
105
+ output.push GreenHat::Paper.new(data: x, flags: flags).render
106
+ end
107
+ else
108
+ output.push GreenHat::Paper.new(data: value, flags: flags).render
109
+ end
110
+ end
111
+
112
+ # Line Break Helper
113
+ def br(_ = nil)
114
+ puts
115
+ end
116
+
117
+ # Store for potential Paginated Response
118
+ def puts(text = '')
119
+ output.push text
120
+ end
121
+
122
+ # Accessor Helper
123
+ def archive
124
+ archive
125
+ end
126
+
127
+ def gitlab_version
128
+ thing = archive.thing? 'gitlab/version-manifest.json'
129
+ return unless thing
130
+
131
+ puts "#{'GitLab Version'.pastel(:cyan)}: #{thing.data.build_version}"
132
+ end
133
+
134
+ # ==============================================================================
135
+ # General Helpers
136
+ # ==============================================================================
137
+ # Indentation Helper
138
+ def indent(text, indent_value = 2)
139
+ if indent_value.zero?
140
+ text
141
+ else
142
+ "#{' ' * indent_value}#{text}"
143
+ end
144
+ end
145
+
146
+ def percent(value, total)
147
+ ((value / total.to_f) * 100).round
148
+ end
149
+
150
+ # Number Helper
151
+ def human_size_to_number(value)
152
+ GreenHat::ShellHelper.human_size_to_number value
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,51 @@
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
+ { header: 'Gitaly', base: 'gitaly/current --level=error --truncate=99', stats: 'error' }
28
+ ]
29
+
30
+ # Filter Logic
31
+ filters = args_select(:filter)
32
+ unless filters.empty?
33
+ entries.select! do |entry|
34
+ filters.any? do |filter|
35
+ !filter.bang if filter.value.split(',').any? { |x| entry.header.downcase.include? x }
36
+ end
37
+ end
38
+ end
39
+
40
+ # Return output
41
+ entries.each do |entry|
42
+ query_string = flags[:verbose] ? entry.base : entry.base + " --stats=#{entry.stats}"
43
+ query(query_string, false) do |results|
44
+ next if results.empty?
45
+ next if !flags[:verbose] && results.map(&:values).map(&:first).sum.zero?
46
+
47
+ header entry.header
48
+
49
+ show results
50
+ end
51
+ 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)