greenhat 0.3.4 → 0.5.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/bin/greenhat +2 -6
  3. data/lib/greenhat/accessors/disk.rb +1 -3
  4. data/lib/greenhat/accessors/gitlab.rb +7 -2
  5. data/lib/greenhat/accessors/memory.rb +1 -1
  6. data/lib/greenhat/archive.rb +19 -8
  7. data/lib/greenhat/cli.rb +35 -135
  8. data/lib/greenhat/entrypoint.rb +170 -0
  9. data/lib/greenhat/host.rb +25 -37
  10. data/lib/greenhat/settings.rb +33 -5
  11. data/lib/greenhat/shell/args.rb +22 -9
  12. data/lib/greenhat/shell/faststats.rb +23 -3
  13. data/lib/greenhat/shell/field_helper.rb +1 -1
  14. data/lib/greenhat/shell/filter_help.rb +217 -162
  15. data/lib/greenhat/shell/gitlab.rb +1 -0
  16. data/lib/greenhat/shell/log.rb +150 -25
  17. data/lib/greenhat/shell/markdown.rb +21 -25
  18. data/lib/greenhat/shell/old_search_helper.rb +54 -0
  19. data/lib/greenhat/shell/page.rb +1 -1
  20. data/lib/greenhat/shell/pipe.rb +31 -0
  21. data/lib/greenhat/shell/platform.rb +28 -0
  22. data/lib/greenhat/shell/query.rb +378 -0
  23. data/lib/greenhat/shell/report.rb +76 -24
  24. data/lib/greenhat/shell/shell_helper.rb +42 -393
  25. data/lib/greenhat/shell.rb +19 -4
  26. data/lib/greenhat/thing/file_types.rb +51 -1
  27. data/lib/greenhat/thing/formatters/api_json.rb +4 -2
  28. data/lib/greenhat/thing/formatters/bracket_log.rb +1 -1
  29. data/lib/greenhat/thing/formatters/colon_split_strip.rb +2 -2
  30. data/lib/greenhat/thing/formatters/dotenv.rb +1 -1
  31. data/lib/greenhat/thing/formatters/format.rb +0 -11
  32. data/lib/greenhat/thing/formatters/free_m.rb +2 -2
  33. data/lib/greenhat/thing/formatters/json.rb +43 -15
  34. data/lib/greenhat/thing/formatters/json_shellwords.rb +3 -2
  35. data/lib/greenhat/thing/formatters/kube_json.rb +3 -2
  36. data/lib/greenhat/thing/formatters/multiline_json.rb +1 -1
  37. data/lib/greenhat/thing/formatters/nginx.rb +11 -3
  38. data/lib/greenhat/thing/formatters/table.rb +3 -3
  39. data/lib/greenhat/thing/formatters/time_space.rb +0 -16
  40. data/lib/greenhat/thing/helpers.rb +12 -11
  41. data/lib/greenhat/thing/info_format.rb +4 -4
  42. data/lib/greenhat/thing/kind.rb +5 -0
  43. data/lib/greenhat/thing/super_log.rb +0 -101
  44. data/lib/greenhat/thing.rb +31 -25
  45. data/lib/greenhat/version.rb +1 -1
  46. data/lib/greenhat/views/api.slim +55 -0
  47. data/lib/greenhat/views/chart.slim +42 -0
  48. data/lib/greenhat/views/chart_template.slim +31 -0
  49. data/lib/greenhat/views/chartkick.js +21 -0
  50. data/lib/greenhat/views/css.slim +47 -0
  51. data/lib/greenhat/views/gitaly.slim +53 -0
  52. data/lib/greenhat/views/headers.slim +16 -0
  53. data/lib/greenhat/views/index-old.slim +51 -0
  54. data/lib/greenhat/views/index.slim +14 -14
  55. data/lib/greenhat/views/info.slim +17 -18
  56. data/lib/greenhat/views/production.slim +55 -0
  57. data/lib/greenhat/views/sidekiq.slim +55 -0
  58. data/lib/greenhat/views/time.slim +63 -0
  59. data/lib/greenhat/views/workhorse.slim +16 -0
  60. data/lib/greenhat/web/api.rb +94 -0
  61. data/lib/greenhat/web/chartkick_shim.rb +14 -0
  62. data/lib/greenhat/web/faststats.rb +44 -0
  63. data/lib/greenhat/web/gitaly.rb +65 -0
  64. data/lib/greenhat/web/helpers.rb +198 -0
  65. data/lib/greenhat/web/production.rb +104 -0
  66. data/lib/greenhat/web/sidekiq.rb +73 -0
  67. data/lib/greenhat/web/stats_helpers.rb +74 -0
  68. data/lib/greenhat/web/time.rb +36 -0
  69. data/lib/greenhat/web/workhorse.rb +43 -0
  70. data/lib/greenhat/web.rb +63 -19
  71. data/lib/greenhat.rb +2 -0
  72. metadata +74 -5
@@ -2,7 +2,9 @@ module GreenHat
2
2
  # CLI Helper
3
3
  module Shell
4
4
  # Logs
5
+ # rubocop:disable Metrics/ModuleLength
5
6
  module Log
7
+ extend Spinner # TODO: DEBUGGING REMOVE
6
8
  def self.auto_complete(list, word)
7
9
  # Argument Parsing
8
10
  files, flags, _args = Args.parse(list)
@@ -19,8 +21,8 @@ module GreenHat
19
21
  elsif matches.count > 1
20
22
  puts "#{'Filter Options:'.pastel(:bright_blue)} #{matches.join(' ').pastel(:bright_green)}"
21
23
  "--#{Cli.common_substr(matches)}"
22
- # -----------------------------------
23
- # TODO: Fix Icky Double Nesting
24
+ # -----------------------------------
25
+ # TODO: Fix Icky Double Nesting
24
26
  elsif files.count.nonzero?
25
27
  matches = FieldHelper.fields_find(files, word, flags)
26
28
 
@@ -58,25 +60,30 @@ module GreenHat
58
60
  puts " General full text by file searching. See #{'search_help'.pastel(:blue)}"
59
61
  puts
60
62
 
63
+ puts ' save'.pastel(:green)
64
+ puts ' Save the last query result into a new searchable object'
65
+ puts
66
+
67
+ puts ' write'.pastel(:green)
68
+ puts ' Write the last query result into a local file'
69
+ puts
70
+
71
+ puts ' visualize'.pastel(:green)
72
+ puts ' Load web services and formulate last query for the UI'
73
+ puts
74
+
61
75
  puts ShellHelper::List.help
62
76
 
63
77
  puts "See #{'examples'.pastel(:bright_blue)} for query examples"
64
78
  end
65
79
 
66
- def self.filter_help(args = {})
67
- if args.empty?
68
- ShellHelper::Filter.help
69
- else
70
- list = ShellHelper::Filter.help_index.select do |k, _v|
71
- k.to_s.include? args.first
72
- end
73
-
74
- puts list.values.map { |x| x.join("\n") }.join("\n\n")
75
- end
80
+ def self.filter_help(raw = {})
81
+ args, flags, _args = Args.parse(raw)
82
+ ShellHelper.show(ShellHelper::Filter.help(args.first), flags)
76
83
  end
77
84
 
78
85
  def self.ls(args = [])
79
- ShellHelper::List.list(args, ShellHelper::Log.list)
86
+ ShellHelper::List.list(args, Thing.list)
80
87
  end
81
88
 
82
89
  def self.show(raw = {})
@@ -89,6 +96,95 @@ module GreenHat
89
96
  ShellHelper.show files.map(&:data).flatten
90
97
  end
91
98
 
99
+ def self.save(raw = [])
100
+ if ShellHelper::Log.last.nil?
101
+ puts 'No previous query found'.pastel(:red)
102
+ puts 'Run a query first then save to store as a new log'
103
+ puts
104
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
105
+ true
106
+ end
107
+
108
+ name = if raw.empty?
109
+ Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
110
+ else
111
+ raw.first
112
+ end
113
+
114
+ if name.blank?
115
+ puts 'Name required'.pastel(:red)
116
+ return true
117
+ end
118
+
119
+ results = ShellHelper.filter_internal ShellHelper::Log.last
120
+
121
+ # Don't save empty results
122
+ if results.empty?
123
+ puts 'No results'.pastel(:red)
124
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
125
+ return false
126
+ end
127
+
128
+ Thing.new.query_save(results, name)
129
+ puts "#{name.pastel(:green)} Saved!"
130
+ end
131
+
132
+ def self.write(raw = [])
133
+ if ShellHelper::Log.last.nil?
134
+ puts 'No previous query found'.pastel(:red)
135
+ puts 'Run a query first then write'
136
+ puts
137
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
138
+ true
139
+ end
140
+
141
+ name = if raw.empty?
142
+ Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
143
+ else
144
+ raw.first
145
+ end
146
+
147
+ if name.blank?
148
+ puts 'Name required'.pastel(:red)
149
+ return true
150
+ end
151
+
152
+ results = ShellHelper.filter_internal ShellHelper::Log.last
153
+
154
+ # Don't save empty results
155
+ if results.empty?
156
+ puts 'No results'.pastel(:red)
157
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
158
+ return false
159
+ end
160
+
161
+ all = results.map { |row| Oj.dump(row) }
162
+ File.write(name, all.join("\n"))
163
+ puts "#{name.pastel(:green)} File Written!"
164
+ end
165
+
166
+ def self.visualize
167
+ if ShellHelper::Log.last.nil?
168
+ puts 'No previous query found'.pastel(:red)
169
+ puts 'Run a query first then visualize to load it in the chart web page'
170
+ puts
171
+ puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'visualize'.pastel(:green)}"
172
+ return true
173
+ end
174
+
175
+ # Load Required Files
176
+ require 'greenhat/web'
177
+
178
+ unless GreenHat::Web.alive?
179
+ GreenHat::Web.start
180
+ sleep 0.2
181
+ end
182
+
183
+ url = "http://localhost:4567/chart/time?query=#{CGI.escape(ShellHelper::Log.last)}"
184
+
185
+ GreenHat::Platform.open url
186
+ end
187
+
92
188
  # ========================================================================
93
189
  # Filter (See Filter Help)
94
190
  # ========================================================================
@@ -99,21 +195,23 @@ module GreenHat
99
195
  def self.filter(raw)
100
196
  # Print Helper
101
197
  if raw == ['help']
102
- filter_help
198
+ filter_help(raw)
103
199
  return true
104
200
  end
105
201
 
202
+ ShellHelper::Log.last = raw
203
+
106
204
  # Argument Parsing
107
205
  files, flags, args = Args.parse(raw)
108
206
 
109
207
  # Prepare Log List
110
- files = ShellHelper.prepare_list(files, ShellHelper::Log.list, flags)
208
+ files = ShellHelper.prepare_list(files)
111
209
 
112
- results = ShellHelper.filter_start(files, flags, args)
210
+ results = Query.start(files, flags, args)
113
211
 
114
212
  # Skip and Print Total if set
115
213
  if flags[:total]
116
- ShellHelper.total_count(results)
214
+ ShellHelper.total_count(results, flags)
117
215
  return true
118
216
  end
119
217
 
@@ -126,13 +224,13 @@ module GreenHat
126
224
  # Check Search Results
127
225
  if results.instance_of?(Hash) && results.values.flatten.empty?
128
226
  puts 'No results'.pastel(:red)
227
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
228
+ elsif flags[:pipe]
229
+ Pipe.show(results, flags[:pipe])
129
230
  else
130
231
  # This causes the key 'colorized' output to also be included
131
232
  ShellHelper.show(results.to_a.compact.flatten, flags)
132
233
  end
133
-
134
- # log filter --path='cloud/gitlab-automation' --path='/pull' --all
135
- # log filter --project=thingy --other_filter=asdf *
136
234
  rescue StandardError => e
137
235
  LogBot.fatal('Filter', message: e.message)
138
236
  ap e.backtrace
@@ -161,9 +259,25 @@ module GreenHat
161
259
  puts 'Count/% occurences for both user and remote ip fields'.pastel(:bright_green)
162
260
  puts 'gitlab-rails/api_json.log --stats=meta.user,meta.remote_ip --exists=meta.user'
163
261
  puts
164
- end
165
262
 
263
+ puts 'Sidekiq jobs that took over 5 seconds excluding LdapSyncWorker jobs'.pastel(:bright_green)
264
+ puts 'sidekiq/current --duration_s>=5 --class!=LdapSyncWorker'
265
+ puts
266
+
267
+ puts 'Search access logs for runner requests, exclude specific runner version'.pastel(:bright_green)
268
+ puts 'nginx/gitlab_access.log --http_user_agent=gitlab-runner --http_user_agent!=13.12.0'
269
+ puts
270
+
271
+ puts 'Get a list of unique Gitaly error messages for a specific project'.pastel(:bright_green)
272
+ puts 'filter --level=error --grpc.request.glProjectPath=path/to/project gitaly/current --slice=error --uniq=error'
273
+ puts
274
+
275
+ puts 'Show workhorse duration/URI. Filter by duration bounds'.pastel(:bright_green)
276
+ puts 'gitlab-workhorse/current --duration_ms>=30000 --duration_ms<=45000 --slice=duration_ms,uri'
277
+ puts
278
+ end
166
279
  # rubocop:enable Layout/LineLength
280
+
167
281
  # ========================================================================
168
282
  # Search (Full Text / String Search)
169
283
  # ========================================================================
@@ -178,13 +292,14 @@ module GreenHat
178
292
 
179
293
  # Skip and Print Total if set
180
294
  if flags[:total]
181
- ShellHelper.total_count(results)
295
+ ShellHelper.total_count(results, flags)
182
296
  return true
183
297
  end
184
298
 
185
299
  # Check Search Results
186
300
  if results.values.flatten.empty?
187
301
  puts 'No results'.pastel(:red)
302
+ ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
188
303
  else
189
304
  # This causes the key 'colorized' output to also be included
190
305
  ShellHelper.show(results.to_a.compact.flatten, flags)
@@ -249,7 +364,7 @@ module GreenHat
249
364
  puts 'log search --text=BuildHooksWorker --text!=start --slice=enqueued_at sidekiq/current'
250
365
  puts
251
366
  end
252
- # rubocop:enable Metrics/MethodLength
367
+ # rubocop:enable Metrics/MethodLength,Metrics/ModuleLength
253
368
 
254
369
  # ------------------------------------------------------------------------
255
370
  end
@@ -260,10 +375,20 @@ module GreenHat
260
375
  module ShellHelper
261
376
  # Log Helpers
262
377
  module Log
263
- def self.list
264
- Thing.all.select(&:log)
378
+ def self.last=(value)
379
+ @last = value.join(' ')
380
+ end
381
+
382
+ def self.last
383
+ @last
384
+ end
385
+
386
+ def self.no_files_warning(files)
387
+ puts "No matching files found for pattern #{files.to_s.pastel(:yellow)}"
388
+ puts "See #{'ls'.pastel(:blue)} for available files"
265
389
  end
266
390
  end
391
+
267
392
  # --------
268
393
  end
269
394
  end
@@ -58,6 +58,9 @@ module GreenHat
58
58
  ''
59
59
  ]
60
60
 
61
+ # GitLab Version
62
+ output << "**GitLab #{gitlab_version}**\n" if gitlab_manifest || gitlab_status
63
+
61
64
  # OS
62
65
  output << "**OS**\n"
63
66
 
@@ -79,8 +82,6 @@ module GreenHat
79
82
  end
80
83
 
81
84
  # Gitlab
82
- output << "**GitLab**\n" if gitlab_manifest || gitlab_status
83
- output << gitlab_version if gitlab_manifest
84
85
  output << gitlab_services if gitlab_status
85
86
 
86
87
  output << ''
@@ -127,8 +128,11 @@ module GreenHat
127
128
  output << exception_errors if exceptions_log
128
129
  output << gitaly_errors if gitaly_log
129
130
 
130
- # Keep Alphabetical Sort
131
- groups = output.each_slice((output.size / 3.to_f).round).to_a
131
+ # Keep Alphabetical Sort / Allow for only one
132
+ slice_size = (output.size / 3.to_f).round
133
+ slice_size = 1 unless slice_size.positive?
134
+
135
+ groups = output.each_slice(slice_size).to_a
132
136
 
133
137
  table = TTY::Table.new do |t|
134
138
  loop do
@@ -169,7 +173,8 @@ module GreenHat
169
173
  results = ShellHelper.filter_internal([
170
174
  'gitlab-rails/application_json.log',
171
175
  '--message!="Cannot obtain an exclusive lease"',
172
- '--severity=error'
176
+ '--severity=error',
177
+ "--archive=#{archive.name}"
173
178
  ].join(' '))
174
179
 
175
180
  "Application: #{results.count}"
@@ -192,7 +197,13 @@ module GreenHat
192
197
  end
193
198
 
194
199
  def gitlab_version
195
- "Version: #{gitlab_manifest.data.build_version}\n"
200
+ txt = gitlab_manifest.data.dig(:software, :'gitlab-rails', :display_version) || gitlab_manifest.data.build_version
201
+
202
+ if txt.include? '-ce'
203
+ txt += ' - [😱 CE](https://about.gitlab.com/support/statement-of-support.html#free-and-community-edition-users)!'
204
+ end
205
+
206
+ "Version: #{txt}"
196
207
  end
197
208
 
198
209
  def hostname
@@ -285,21 +296,6 @@ module GreenHat
285
296
  ].join
286
297
  end
287
298
 
288
- # def memory_perc
289
- # total = ShellHelper.human_size_to_number(meminfo.data['MemTotal'])
290
- # free = ShellHelper.human_size_to_number(meminfo.data['MemFree'])
291
- # used = percent((total - free), total)
292
-
293
- # [
294
- # title('Usage'),
295
- # ' ['.pastel(:bright_black),
296
- # '='.pastel(:green) * (used / 2),
297
- # ' ' * (50 - used / 2),
298
- # ']'.pastel(:bright_black),
299
- # " #{100 - percent(free, total)}%".pastel(:green) # Inverse
300
- # ].join
301
- # end
302
-
303
299
  def memory_free
304
300
  free = free_m.data.find { |x| x.kind == 'Mem' }
305
301
 
@@ -307,10 +303,10 @@ module GreenHat
307
303
 
308
304
  pad = 6
309
305
  list = [
310
- "#{title('Total', pad)} #{number_to_human_size(free.total.to_i * 1024**2)}",
311
- "#{title('Used', pad)} #{number_to_human_size(free.used.to_i * 1024**2)}",
312
- "#{title('Free', pad)} #{number_to_human_size(free.free.to_i * 1024**2)}",
313
- "#{title('Avail', pad)} #{number_to_human_size(free.available.to_i * 1024**2)}"
306
+ "#{title('Total', pad)} #{number_to_human_size(free.total.to_i * (1024**2))}",
307
+ "#{title('Used', pad)} #{number_to_human_size(free.used.to_i * (1024**2))}",
308
+ "#{title('Free', pad)} #{number_to_human_size(free.free.to_i * (1024**2))}",
309
+ "#{title('Avail', pad)} #{number_to_human_size(free.available.to_i * (1024**2))}"
314
310
  ]
315
311
 
316
312
  # Keep Alphabetical Sort
@@ -0,0 +1,54 @@
1
+ module GreenHat
2
+ # Deprecating search stuff
3
+ module ShellHelper
4
+ # Main Entry Point for Searching
5
+ # def self.search_start(log_list, filter_type, args, opts)
6
+ def self.search_start(files, flags, args)
7
+ # Convert to Things
8
+ logs = ShellHelper.find_things(files, flags)
9
+
10
+ logs.each_with_object({}) do |log, obj|
11
+ # Ignore Empty Results / No Thing
12
+ next if log&.data.blank?
13
+
14
+ obj[log.friendly_name] = ShellHelper.search(log.data, flags, args)
15
+
16
+ obj
17
+ end
18
+ end
19
+
20
+ # Generic Search Helper / String/Regex
21
+ def self.search(data, flags = {}, args = {})
22
+ results = data.clone.flatten.compact
23
+ results.select! do |row|
24
+ args.send(flags.logic) do |arg|
25
+ search_row(row, arg, flags)
26
+ end
27
+ end
28
+
29
+ # Strip Results if Slice is defined
30
+ results.map! { |row| row.slice(*flags[:slice]) } if flags[:slice]
31
+
32
+ # Strip Results if Except is defined
33
+ results.map! { |row| row.except(*flags[:except]) } if flags[:except]
34
+
35
+ # Remove Blank from either slice or except
36
+ results.reject!(&:empty?)
37
+
38
+ results
39
+ end
40
+
41
+ # Break out filter row logic into separate method
42
+ def self.search_row(row, arg, flags)
43
+ # Sensitivity Check / Check for Match
44
+ included = filter_row_entry(row.to_s, arg, flags)
45
+
46
+ # Pivot of off include vs exclude
47
+ if arg.bang
48
+ !included
49
+ else
50
+ included
51
+ end
52
+ end
53
+ end
54
+ end
@@ -8,7 +8,7 @@ module GreenHat
8
8
  # Pass if Explicitly Set / Inverse for skip
9
9
  return !flags[:page] if flags.key? :page
10
10
 
11
- LogBot.debug('Page', count_rows(data, flags)) if ENV['DEBUG']
11
+ LogBot.debug('Page Skip', count_rows(data, flags)) if ENV['DEBUG']
12
12
 
13
13
  count_rows(data, flags)
14
14
  end
@@ -0,0 +1,31 @@
1
+ module GreenHat
2
+ # Helper for piping arguments
3
+ module Pipe
4
+ def self.show(results, arg)
5
+ uuid = SecureRandom.uuid # File Placeholder
6
+ write(results, uuid)
7
+ command(uuid, arg)
8
+ delete(uuid)
9
+ end
10
+
11
+ # Final execution into pipe
12
+ def self.command(uuid, arg)
13
+ puts `cat "#{file(uuid)}" | #{arg}`
14
+ end
15
+
16
+ # File path helper
17
+ def self.file(uuid)
18
+ "#{$TMP}/#{uuid}.txt"
19
+ end
20
+
21
+ # Helper to write all into split lines for pipe
22
+ def self.write(results, uuid)
23
+ File.write(file(uuid), results.map(&:flatten).flatten.join("\n"))
24
+ end
25
+
26
+ # Clean up created file
27
+ def self.delete(uuid)
28
+ File.delete file(uuid)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ module GreenHat
2
+ # Common Helpers
3
+ module Platform
4
+ def self.platform
5
+ if @platform
6
+ @platform
7
+ else
8
+ require 'tty-platform'
9
+ @platform ||= TTY::Platform.new
10
+
11
+ end
12
+ end
13
+
14
+ def self.open(url = 'http://localhost:4567/chart/time')
15
+ cmd = if platform.linux? || platform.unix?
16
+ 'xdg-open'
17
+ elsif platform.mac?
18
+ 'open'
19
+ end
20
+
21
+ # platform.windows? # => false
22
+ # platform.unix? # => true
23
+ # platform.linux? # => false
24
+ # platform.mac? # => true
25
+ system("#{cmd} '#{url}'")
26
+ end
27
+ end
28
+ end