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.
- checksums.yaml +4 -4
- data/lib/greenhat/accessors/disk.rb +42 -3
- data/lib/greenhat/accessors/gitlab.rb +30 -1
- data/lib/greenhat/accessors/memory.rb +1 -1
- data/lib/greenhat/archive.rb +11 -2
- data/lib/greenhat/cli.rb +37 -0
- data/lib/greenhat/host.rb +25 -37
- data/lib/greenhat/shell/args.rb +22 -9
- data/lib/greenhat/shell/faststats.rb +23 -3
- data/lib/greenhat/shell/field_helper.rb +1 -1
- data/lib/greenhat/shell/filter_help.rb +232 -9
- data/lib/greenhat/shell/log.rb +153 -9
- data/lib/greenhat/shell/markdown.rb +352 -0
- data/lib/greenhat/shell/old_search_helper.rb +54 -0
- data/lib/greenhat/shell/page.rb +1 -1
- data/lib/greenhat/shell/pipe.rb +31 -0
- data/lib/greenhat/shell/platform.rb +28 -0
- data/lib/greenhat/shell/report.rb +106 -25
- data/lib/greenhat/shell/shell_helper.rb +114 -106
- data/lib/greenhat/shell.rb +7 -0
- data/lib/greenhat/thing/file_types.rb +126 -7
- data/lib/greenhat/thing/formatters/json.rb +4 -0
- data/lib/greenhat/thing/formatters/kube_json.rb +36 -0
- data/lib/greenhat/thing/formatters/kube_nginx.rb +48 -0
- data/lib/greenhat/thing/formatters/kube_webservice.rb +51 -0
- data/lib/greenhat/thing/formatters/nginx.rb +6 -2
- data/lib/greenhat/thing/formatters/registry.rb +47 -0
- data/lib/greenhat/thing/formatters/time_space.rb +0 -16
- data/lib/greenhat/thing/helpers.rb +12 -0
- data/lib/greenhat/thing.rb +10 -0
- data/lib/greenhat/version.rb +1 -1
- data/lib/greenhat/views/api.slim +55 -0
- data/lib/greenhat/views/chart.slim +42 -0
- data/lib/greenhat/views/chart_template.slim +31 -0
- data/lib/greenhat/views/chartkick.js +21 -0
- data/lib/greenhat/views/css.slim +47 -0
- data/lib/greenhat/views/gitaly.slim +53 -0
- data/lib/greenhat/views/headers.slim +16 -0
- data/lib/greenhat/views/index-old.slim +51 -0
- data/lib/greenhat/views/index.slim +14 -14
- data/lib/greenhat/views/info.slim +17 -18
- data/lib/greenhat/views/production.slim +55 -0
- data/lib/greenhat/views/sidekiq.slim +55 -0
- data/lib/greenhat/views/time.slim +63 -0
- data/lib/greenhat/views/workhorse.slim +16 -0
- data/lib/greenhat/web/api.rb +94 -0
- data/lib/greenhat/web/chartkick_shim.rb +14 -0
- data/lib/greenhat/web/faststats.rb +44 -0
- data/lib/greenhat/web/gitaly.rb +65 -0
- data/lib/greenhat/web/helpers.rb +198 -0
- data/lib/greenhat/web/production.rb +104 -0
- data/lib/greenhat/web/sidekiq.rb +73 -0
- data/lib/greenhat/web/stats_helpers.rb +74 -0
- data/lib/greenhat/web/time.rb +36 -0
- data/lib/greenhat/web/workhorse.rb +43 -0
- data/lib/greenhat/web.rb +63 -19
- data/lib/greenhat.rb +1 -0
- 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
|
data/lib/greenhat/shell/page.rb
CHANGED
@@ -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
|
-
|
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, :
|
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.
|
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 ||
|
94
|
+
output << title('Errors') if production_log || api_log || sidekiq_log
|
88
95
|
output << production_errors if production_log
|
89
|
-
output <<
|
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
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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
|