greenhat 0.3.2 → 0.3.6

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/lib/greenhat/accessors/disk.rb +42 -3
  3. data/lib/greenhat/accessors/gitlab.rb +30 -1
  4. data/lib/greenhat/accessors/memory.rb +1 -1
  5. data/lib/greenhat/archive.rb +11 -2
  6. data/lib/greenhat/cli.rb +37 -0
  7. data/lib/greenhat/host.rb +25 -37
  8. data/lib/greenhat/shell/args.rb +22 -9
  9. data/lib/greenhat/shell/faststats.rb +23 -3
  10. data/lib/greenhat/shell/field_helper.rb +1 -1
  11. data/lib/greenhat/shell/filter_help.rb +232 -9
  12. data/lib/greenhat/shell/log.rb +153 -9
  13. data/lib/greenhat/shell/markdown.rb +352 -0
  14. data/lib/greenhat/shell/old_search_helper.rb +54 -0
  15. data/lib/greenhat/shell/page.rb +1 -1
  16. data/lib/greenhat/shell/pipe.rb +31 -0
  17. data/lib/greenhat/shell/platform.rb +28 -0
  18. data/lib/greenhat/shell/report.rb +106 -25
  19. data/lib/greenhat/shell/shell_helper.rb +114 -106
  20. data/lib/greenhat/shell.rb +7 -0
  21. data/lib/greenhat/thing/file_types.rb +126 -7
  22. data/lib/greenhat/thing/formatters/json.rb +4 -0
  23. data/lib/greenhat/thing/formatters/kube_json.rb +36 -0
  24. data/lib/greenhat/thing/formatters/kube_nginx.rb +48 -0
  25. data/lib/greenhat/thing/formatters/kube_webservice.rb +51 -0
  26. data/lib/greenhat/thing/formatters/nginx.rb +6 -2
  27. data/lib/greenhat/thing/formatters/registry.rb +47 -0
  28. data/lib/greenhat/thing/formatters/time_space.rb +0 -16
  29. data/lib/greenhat/thing/helpers.rb +12 -0
  30. data/lib/greenhat/thing.rb +10 -0
  31. data/lib/greenhat/version.rb +1 -1
  32. data/lib/greenhat/views/api.slim +55 -0
  33. data/lib/greenhat/views/chart.slim +42 -0
  34. data/lib/greenhat/views/chart_template.slim +31 -0
  35. data/lib/greenhat/views/chartkick.js +21 -0
  36. data/lib/greenhat/views/css.slim +47 -0
  37. data/lib/greenhat/views/gitaly.slim +53 -0
  38. data/lib/greenhat/views/headers.slim +16 -0
  39. data/lib/greenhat/views/index-old.slim +51 -0
  40. data/lib/greenhat/views/index.slim +14 -14
  41. data/lib/greenhat/views/info.slim +17 -18
  42. data/lib/greenhat/views/production.slim +55 -0
  43. data/lib/greenhat/views/sidekiq.slim +55 -0
  44. data/lib/greenhat/views/time.slim +63 -0
  45. data/lib/greenhat/views/workhorse.slim +16 -0
  46. data/lib/greenhat/web/api.rb +94 -0
  47. data/lib/greenhat/web/chartkick_shim.rb +14 -0
  48. data/lib/greenhat/web/faststats.rb +44 -0
  49. data/lib/greenhat/web/gitaly.rb +65 -0
  50. data/lib/greenhat/web/helpers.rb +198 -0
  51. data/lib/greenhat/web/production.rb +104 -0
  52. data/lib/greenhat/web/sidekiq.rb +73 -0
  53. data/lib/greenhat/web/stats_helpers.rb +74 -0
  54. data/lib/greenhat/web/time.rb +36 -0
  55. data/lib/greenhat/web/workhorse.rb +43 -0
  56. data/lib/greenhat/web.rb +63 -19
  57. data/lib/greenhat.rb +1 -0
  58. metadata +73 -2
@@ -0,0 +1,352 @@
1
+ module GreenHat
2
+ # Root Level Shell / Report Helper
3
+ module Shell
4
+ def self.markdown_report(raw)
5
+ _files, flags, _args = Args.parse(raw)
6
+
7
+ archives = if flags.archive
8
+ Archive.all.select do |archive|
9
+ flags.archive.any? { |x| archive.name.include? x.to_s }
10
+ end
11
+ else
12
+ Archive.all
13
+ end
14
+
15
+ ShellHelper.show(archives.map(&:report_markdown).map(&:show).flatten, flags)
16
+ end
17
+ end
18
+ end
19
+
20
+ module GreenHat
21
+ # Report Generator Helper
22
+ # rubocop:disable Metrics/ClassLength
23
+ class ReportMarkdown
24
+ include ActionView::Helpers::NumberHelper
25
+
26
+ attr_accessor :archive, :host, :os_release, :selinux_status, :cpu, :uname,
27
+ :timedatectl, :uptime, :meminfo, :gitlab_manifest, :gitlab_status,
28
+ :production_log, :api_log, :application_log, :sidekiq_log,
29
+ :exceptions_log, :gitaly_log, :free_m, :disk_free
30
+
31
+ # Find Needed Files for Report
32
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
33
+ def initialize(archive)
34
+ self.archive = archive
35
+ self.host = archive.things.find { |x| x.name == 'hostname' }
36
+ self.os_release = archive.things.find { |x| x.name == 'etc/os-release' }
37
+ self.selinux_status = archive.things.find { |x| x.name == 'sestatus' }
38
+ self.cpu = archive.things.find { |x| x.name == 'lscpu' }
39
+ self.uname = archive.things.find { |x| x.name == 'uname' }
40
+ self.timedatectl = archive.things.find { |x| x.name == 'timedatectl' }
41
+ self.uptime = archive.things.find { |x| x.name == 'uptime' }
42
+ self.meminfo = archive.things.find { |x| x.name == 'meminfo' }
43
+ self.free_m = archive.things.find { |x| x.name == 'free_m' }
44
+ self.gitlab_manifest = archive.things.find { |x| x.name == 'gitlab/version-manifest.json' }
45
+ self.gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
46
+ self.production_log = archive.things.find { |x| x.name == 'gitlab-rails/production_json.log' }
47
+ self.api_log = archive.things.find { |x| x.name == 'gitlab-rails/api_json.log' }
48
+ self.application_log = archive.things.find { |x| x.name == 'gitlab-rails/application_json.log' }
49
+ self.exceptions_log = archive.things.find { |x| x.name == 'gitlab-rails/exceptions_json.log' }
50
+ self.gitaly_log = archive.things.find { |x| x.name == 'gitaly/current' }
51
+ self.sidekiq_log = archive.things.find { |x| x.name == 'sidekiq/current' }
52
+ self.disk_free = archive.things.find { |x| x.name == 'df_h' }
53
+ end
54
+
55
+ def show
56
+ output = [
57
+ archive.friendly_name,
58
+ ''
59
+ ]
60
+
61
+ # GitLab Version
62
+ output << "**GitLab #{gitlab_version}**\n" if gitlab_manifest || gitlab_status
63
+
64
+ # OS
65
+ output << "**OS**\n"
66
+
67
+ output << collect_host
68
+ output << ''
69
+
70
+ # Memory
71
+ if meminfo || free_m
72
+ output << "**Memory**\n"
73
+ # output << memory_perc if meminfo
74
+ output << memory_free if free_m
75
+ output << ''
76
+ end
77
+
78
+ # Disk
79
+ if disk_free
80
+ output << disks
81
+ output << ''
82
+ end
83
+
84
+ # Gitlab
85
+ output << gitlab_services if gitlab_status
86
+
87
+ output << ''
88
+
89
+ output << "**Errors**\n" if production_log || api_log || application_log || sidekiq_log
90
+ output << collect_errors
91
+
92
+ # Final Space / Return
93
+ output << ''
94
+ output
95
+ end
96
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
97
+
98
+ def collect_host
99
+ output = []
100
+ output << hostname if host
101
+ output << distro if os_release
102
+ output << selinux if selinux_status
103
+ # output << arch if cpu
104
+ output << kernel if uname
105
+ output << sys_time if timedatectl
106
+ output << sys_uptime if uptime
107
+ output << load_average if uptime && cpu
108
+
109
+ groups = output.each_slice((output.size / 2.to_f).round).to_a
110
+
111
+ table = TTY::Table.new do |t|
112
+ loop do
113
+ break if groups.all?(&:empty?)
114
+
115
+ t << groups.map(&:shift)
116
+ end
117
+ end
118
+
119
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
120
+ end
121
+
122
+ def collect_errors
123
+ output = []
124
+ output << production_errors if production_log
125
+ output << application_errors if application_log
126
+ output << sidekiq_errors if sidekiq_log
127
+ output << api_errors if api_log
128
+ output << exception_errors if exceptions_log
129
+ output << gitaly_errors if gitaly_log
130
+
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
136
+
137
+ table = TTY::Table.new do |t|
138
+ loop do
139
+ break if groups.all?(&:empty?)
140
+
141
+ t << groups.map(&:shift)
142
+ end
143
+ end
144
+
145
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
146
+ end
147
+
148
+ def exception_errors
149
+ count = exceptions_log.data.count
150
+
151
+ "Exception: #{count}"
152
+ end
153
+
154
+ def gitaly_errors
155
+ count = gitaly_log.data.count { |x| x.level == 'error' }
156
+
157
+ "Gitaly: #{count}"
158
+ end
159
+
160
+ def production_errors
161
+ count = production_log.data.count { |x| x.status == 500 }
162
+
163
+ "Production: #{count}"
164
+ end
165
+
166
+ def api_errors
167
+ count = api_log.data.count { |x| x.status == 500 }
168
+
169
+ "API: #{count}"
170
+ end
171
+
172
+ def application_errors
173
+ results = ShellHelper.filter_internal([
174
+ 'gitlab-rails/application_json.log',
175
+ '--message!="Cannot obtain an exclusive lease"',
176
+ '--severity=error',
177
+ "--archive=#{archive.name}"
178
+ ].join(' '))
179
+
180
+ "Application: #{results.count}"
181
+ end
182
+
183
+ def sidekiq_errors
184
+ count = sidekiq_log.data.count { |x| x&.severity == 'ERROR' }
185
+
186
+ "Sidekiq: #{count}"
187
+ end
188
+
189
+ def gitlab_services
190
+ [
191
+ "**Services**\n",
192
+ "\n",
193
+ GreenHat::GitLab.services_markdown(archive)
194
+ ].join
195
+ rescue StandardError => e
196
+ LogBot.fatal('GitLab Services', message: e.message, backtrace: e.backtrace.first)
197
+ end
198
+
199
+ def gitlab_version
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}"
207
+ end
208
+
209
+ def hostname
210
+ "Hostname: #{host.data.first}"
211
+ end
212
+
213
+ def distro
214
+ [
215
+ "Distro: [#{os_release.data.ID}] ",
216
+ os_release.data.PRETTY_NAME
217
+ ].join
218
+ end
219
+
220
+ def selinux
221
+ status = selinux_status.data['SELinux status']
222
+
223
+ [
224
+ 'SeLinux: ',
225
+ status,
226
+ ' (',
227
+ selinux_status.data['Current mode'],
228
+ ')'
229
+ ].join
230
+ end
231
+
232
+ def arch
233
+ [
234
+ 'Arch: ',
235
+ cpu.data.Architecture
236
+ ].join
237
+ end
238
+
239
+ def kernel
240
+ # TODO: Better way to consistently get uname info?
241
+ value, build = uname.data.first.split[2].split('-')
242
+ [
243
+ 'Kernel: ',
244
+ value,
245
+ " (#{build})"
246
+ ].join
247
+ end
248
+
249
+ # Helper for finding if NTP is enabled
250
+ def ntp_keys
251
+ [
252
+ 'Network time on', 'NTP enabled', 'NTP service', 'System clock synchronized'
253
+ ]
254
+ end
255
+
256
+ def sys_time
257
+ # Ignore if Empty
258
+ return false if timedatectl.data.nil?
259
+
260
+ ntp_statuses = timedatectl.data.slice(*ntp_keys).values.compact
261
+
262
+ ntp_status = ntp_statuses.first
263
+
264
+ # Fall Back
265
+ ntp_status ||= 'unknown'
266
+
267
+ [
268
+ 'Sys Time: ',
269
+ timedatectl.data['Local time'],
270
+ "(ntp: #{ntp_status})"
271
+ ].join
272
+ end
273
+
274
+ # Strip/Simplify Uptime
275
+ def sys_uptime
276
+ init = uptime.data.first.split(', load average').first.strip
277
+
278
+ "Uptime: #{init.split('up ', 2).last}"
279
+ end
280
+
281
+ def load_average
282
+ cpu_count = cpu.data['CPU(s)'].to_i
283
+ intervals = uptime.data.first.split('load average: ', 2).last.split(', ').map(&:to_f)
284
+
285
+ # Generate Colorized Text for Output
286
+ intervals_text = intervals.map do |interval|
287
+ value = percent(interval, cpu_count)
288
+
289
+ "#{interval} (#{value}%)"
290
+ end
291
+
292
+ [
293
+ 'LoadAvg: ',
294
+ "[CPU #{cpu_count}] ",
295
+ intervals_text.join(', ')
296
+ ].join
297
+ end
298
+
299
+ def memory_free
300
+ free = free_m.data.find { |x| x.kind == 'Mem' }
301
+
302
+ return unless free
303
+
304
+ pad = 6
305
+ list = [
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))}"
310
+ ]
311
+
312
+ # Keep Alphabetical Sort
313
+ groups = list.each_slice((list.size / 2.to_f).round).to_a
314
+
315
+ table = TTY::Table.new do |t|
316
+ loop do
317
+ break if groups.all?(&:empty?)
318
+
319
+ t << groups.map(&:shift)
320
+ end
321
+ end
322
+
323
+ "```\n#{table.render(:basic, padding: [0, 2, 0, 0])}\n```"
324
+ end
325
+
326
+ def disks
327
+ # GreenHat::Disk.df({archive: []})
328
+ file = GreenHat::Disk.df({ archive: [archive.name] })
329
+
330
+ disk_list = GreenHat::Disk.markdown_format(file.first, false, 3)
331
+
332
+ # Preapre / Indent List
333
+ [
334
+ '**Disks**',
335
+ "\n\n```\n#{disk_list.join("\n")}\n```"
336
+ ].join
337
+ end
338
+
339
+ # ----------------------------
340
+ # Helpers
341
+ # ----------------------------
342
+ def percent(value, total)
343
+ ((value / total.to_f) * 100).round
344
+ end
345
+
346
+ # Helper to Make Cyan Titles
347
+ def title(name, ljust = 16)
348
+ "#{name}:".ljust(ljust)
349
+ end
350
+ end
351
+ # rubocop:enable Metrics/ClassLength
352
+ end
@@ -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
@@ -12,7 +12,11 @@ module GreenHat
12
12
  Archive.all
13
13
  end
14
14
 
15
- ShellHelper.show(archives.map(&:report).map(&:show).flatten, flags)
15
+ output = archives.map { |x| x.report(flags) }.map(&:show).flatten
16
+
17
+ flags[:page] = true if flags.full && !flags.raw
18
+
19
+ ShellHelper.show(output, flags)
16
20
  end
17
21
  end
18
22
  end
@@ -23,14 +27,16 @@ module GreenHat
23
27
  class Report
24
28
  include ActionView::Helpers::NumberHelper
25
29
 
26
- attr_accessor :archive, :host, :os_release, :selinux_status, :cpu, :uname,
30
+ attr_accessor :archive, :flags, :host, :os_release, :selinux_status, :cpu, :uname,
27
31
  :timedatectl, :uptime, :meminfo, :gitlab_manifest, :gitlab_status,
28
- :production_log, :api_log, :application_log, :sidekiq_log, :free_m, :disk_free
32
+ :production_log, :api_log, :sidekiq_log,
33
+ :exceptions_log, :gitaly_log, :free_m, :disk_free
29
34
 
30
35
  # Find Needed Files for Report
31
36
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
32
- def initialize(archive)
37
+ def initialize(archive, flags)
33
38
  self.archive = archive
39
+ self.flags = flags
34
40
  self.host = archive.things.find { |x| x.name == 'hostname' }
35
41
  self.os_release = archive.things.find { |x| x.name == 'etc/os-release' }
36
42
  self.selinux_status = archive.things.find { |x| x.name == 'sestatus' }
@@ -44,7 +50,8 @@ module GreenHat
44
50
  self.gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
45
51
  self.production_log = archive.things.find { |x| x.name == 'gitlab-rails/production_json.log' }
46
52
  self.api_log = archive.things.find { |x| x.name == 'gitlab-rails/api_json.log' }
47
- self.application_log = archive.things.find { |x| x.name == 'gitlab-rails/application_json.log' }
53
+ self.exceptions_log = archive.things.find { |x| x.name == 'gitlab-rails/exceptions_json.log' }
54
+ self.gitaly_log = archive.things.find { |x| x.name == 'gitaly/current' }
48
55
  self.sidekiq_log = archive.things.find { |x| x.name == 'sidekiq/current' }
49
56
  self.disk_free = archive.things.find { |x| x.name == 'df_h' }
50
57
  end
@@ -84,11 +91,16 @@ module GreenHat
84
91
  output << 'GitLab'.pastel(:bright_yellow) if gitlab_manifest
85
92
  output << gitlab_version if gitlab_manifest
86
93
  output << gitlab_services if gitlab_status
87
- output << title('Errors') if production_log || api_log || application_log || sidekiq_log
94
+ output << title('Errors') if production_log || api_log || sidekiq_log
88
95
  output << production_errors if production_log
89
- output << api_errors if api_log
90
- output << application_errors if application_log
96
+ output << application_errors if archive.thing?('gitlab-rails/application_json.log')
91
97
  output << sidekiq_errors if sidekiq_log
98
+ output << api_errors if api_log
99
+ output << exception_errors if exceptions_log
100
+ output << gitaly_errors if gitaly_log
101
+ output << workhorse_errors if archive.thing?('gitlab-workhorse/current')
102
+
103
+ full(output) if flags.full
92
104
 
93
105
  # Final Space / Return
94
106
  output << ''
@@ -96,6 +108,52 @@ module GreenHat
96
108
  end
97
109
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
98
110
 
111
+ def full(output)
112
+ output << ''
113
+ output << 'FastStats Top'
114
+ Shell::Faststats.top(['--raw'], true).each { |x| output << x } # Page Row Helper
115
+ output << ''
116
+
117
+ output << 'FastStats Errors'
118
+ Shell::Faststats.errors(['--raw'], true).each { |x| output << x } # Page Row Helper
119
+ output << ''
120
+ end
121
+
122
+ def exception_errors
123
+ count = exceptions_log.data.count
124
+ color = count.zero? ? :green : :red
125
+
126
+ [
127
+ title(' Exception', :bright_red, 18),
128
+ count.to_s.pastel(color)
129
+ ].join
130
+ end
131
+
132
+ def workhorse_errors
133
+ results = ShellHelper.filter_internal([
134
+ 'gitlab-workhorse/current',
135
+ '--level=error',
136
+ "--archive=#{archive.name}"
137
+ ].join(' '))
138
+
139
+ color = results.count.zero? ? :green : :red
140
+
141
+ [
142
+ title(' Workhorse', :bright_red, 18),
143
+ results.count.to_s.pastel(color)
144
+ ].join
145
+ end
146
+
147
+ def gitaly_errors
148
+ count = gitaly_log.data.count { |x| x.level == 'error' }
149
+ color = count.zero? ? :green : :red
150
+
151
+ [
152
+ title(' Gitaly', :bright_red, 18),
153
+ count.to_s.pastel(color)
154
+ ].join
155
+ end
156
+
99
157
  def production_errors
100
158
  count = production_log.data.count { |x| x.status == 500 }
101
159
  color = count.zero? ? :green : :red
@@ -117,7 +175,14 @@ module GreenHat
117
175
  end
118
176
 
119
177
  def application_errors
120
- count = application_log.data.count { |x| x&.severity == 'ERROR' }
178
+ results = ShellHelper.filter_internal([
179
+ 'gitlab-rails/application_json.log',
180
+ '--message!="Cannot obtain an exclusive lease"',
181
+ '--severity=error',
182
+ "--archive=#{archive.name}"
183
+ ].join(' '))
184
+
185
+ count = results.count { |x| x&.severity == 'ERROR' }
121
186
  color = count.zero? ? :green : :red
122
187
 
123
188
  [
@@ -269,7 +334,7 @@ module GreenHat
269
334
  title('Usage'),
270
335
  ' ['.pastel(:bright_black),
271
336
  '='.pastel(:green) * (used / 2),
272
- ' ' * (50 - used / 2),
337
+ ' ' * (50 - (used / 2)),
273
338
  ']'.pastel(:bright_black),
274
339
  " #{100 - percent(free, total)}%".pastel(:green) # Inverse
275
340
  ].join
@@ -282,21 +347,37 @@ module GreenHat
282
347
 
283
348
  formatted_mem = free_m.data.map { |x| GreenHat::Memory.memory_row x }
284
349
 
285
- [
286
- title('Total', :cyan, 14),
287
- number_to_human_size(free.total.to_i * 1024**2),
288
- "\n",
289
- title('Used', :yellow, 14),
290
- number_to_human_size(free.used.to_i * 1024**2),
291
- "\n",
292
- title('Free', :blue, 14),
293
- number_to_human_size(free.free.to_i * 1024**2),
294
- "\n",
295
- title('Available', :green, 14),
296
- number_to_human_size(free.available.to_i * 1024**2),
297
- "\n\n",
298
- formatted_mem.map { |x| x.prepend ' ' * 2 }.join("\n")
299
- ].join
350
+ output = []
351
+ unless free.total.blank?
352
+ output << title('Total', :cyan, 14)
353
+ output << number_to_human_size(free.total.to_i * (1024**2))
354
+ output << "\n"
355
+ end
356
+
357
+ unless free.total.blank?
358
+ output << title('Used', :yellow, 14)
359
+ output << number_to_human_size(free.used.to_i * (1024**2))
360
+ output << "\n"
361
+ end
362
+
363
+ unless free.total.blank?
364
+ output << title('Free', :blue, 14)
365
+ output << number_to_human_size(free.free.to_i * (1024**2))
366
+ output << "\n"
367
+ end
368
+
369
+ unless free.total.blank?
370
+ output << title('Available', :green, 14)
371
+ output << number_to_human_size(free.available.to_i * (1024**2))
372
+ output << "\n"
373
+ end
374
+
375
+ output << "\n"
376
+ output << formatted_mem.map { |x| x.prepend ' ' * 2 }.join("\n")
377
+
378
+ output.join
379
+ rescue StandardError => e
380
+ LogBot.fatal('Memory', message: e.message, backtrace: e.backtrace.first)
300
381
  end
301
382
 
302
383
  def disks