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.
- checksums.yaml +4 -4
- data/bin/greenhat +2 -6
- data/lib/greenhat/accessors/disk.rb +1 -3
- data/lib/greenhat/accessors/gitlab.rb +7 -2
- data/lib/greenhat/accessors/memory.rb +1 -1
- data/lib/greenhat/archive.rb +19 -8
- data/lib/greenhat/cli.rb +35 -135
- data/lib/greenhat/entrypoint.rb +170 -0
- data/lib/greenhat/host.rb +25 -37
- data/lib/greenhat/settings.rb +33 -5
- 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 +217 -162
- data/lib/greenhat/shell/gitlab.rb +1 -0
- data/lib/greenhat/shell/log.rb +150 -25
- data/lib/greenhat/shell/markdown.rb +21 -25
- 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/query.rb +378 -0
- data/lib/greenhat/shell/report.rb +76 -24
- data/lib/greenhat/shell/shell_helper.rb +42 -393
- data/lib/greenhat/shell.rb +19 -4
- data/lib/greenhat/thing/file_types.rb +51 -1
- data/lib/greenhat/thing/formatters/api_json.rb +4 -2
- data/lib/greenhat/thing/formatters/bracket_log.rb +1 -1
- data/lib/greenhat/thing/formatters/colon_split_strip.rb +2 -2
- data/lib/greenhat/thing/formatters/dotenv.rb +1 -1
- data/lib/greenhat/thing/formatters/format.rb +0 -11
- data/lib/greenhat/thing/formatters/free_m.rb +2 -2
- data/lib/greenhat/thing/formatters/json.rb +43 -15
- data/lib/greenhat/thing/formatters/json_shellwords.rb +3 -2
- data/lib/greenhat/thing/formatters/kube_json.rb +3 -2
- data/lib/greenhat/thing/formatters/multiline_json.rb +1 -1
- data/lib/greenhat/thing/formatters/nginx.rb +11 -3
- data/lib/greenhat/thing/formatters/table.rb +3 -3
- data/lib/greenhat/thing/formatters/time_space.rb +0 -16
- data/lib/greenhat/thing/helpers.rb +12 -11
- data/lib/greenhat/thing/info_format.rb +4 -4
- data/lib/greenhat/thing/kind.rb +5 -0
- data/lib/greenhat/thing/super_log.rb +0 -101
- data/lib/greenhat/thing.rb +31 -25
- 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 +2 -0
- metadata +74 -5
@@ -0,0 +1,378 @@
|
|
1
|
+
# NameSpace
|
2
|
+
module GreenHat
|
3
|
+
# Query Handlers
|
4
|
+
# rubocop:disable Metrics/ModuleLength
|
5
|
+
module Query
|
6
|
+
# Main Entry Point for Filtering
|
7
|
+
def self.start(files, flags = {}, args = {})
|
8
|
+
# Convert to Things
|
9
|
+
files = ShellHelper.find_things(files, flags).select(&:query?)
|
10
|
+
|
11
|
+
# Ignore Archive/Host Dividers
|
12
|
+
if flags[:combine]
|
13
|
+
results = files.reject(&:blank?).map(&:data).flatten.compact
|
14
|
+
Query.filter(results, flags, args)
|
15
|
+
else
|
16
|
+
# Iterate and Preserve Archive/Host Index
|
17
|
+
files.each_with_object({}) do |file, obj|
|
18
|
+
# Ignore Empty Results / No Thing
|
19
|
+
next if file&.blank?
|
20
|
+
|
21
|
+
# Include Total Count in Name
|
22
|
+
results = Query.filter(file.data, flags, args)
|
23
|
+
duration = calculate_duration(results)
|
24
|
+
|
25
|
+
title = [
|
26
|
+
file.friendly_name,
|
27
|
+
" #{results.count}".pastel(:bright_black)
|
28
|
+
|
29
|
+
]
|
30
|
+
|
31
|
+
# Append Duration
|
32
|
+
title.push(" #{duration.pastel(:cyan, :dim)}") unless duration.blank?
|
33
|
+
|
34
|
+
# Save unless empty
|
35
|
+
obj[title.join] = results unless results.count.zero?
|
36
|
+
|
37
|
+
obj
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.calculate_duration(results)
|
43
|
+
# Skip for Pluck
|
44
|
+
only_with_time = results.select { |x| x.instance_of?(Hash) && x.key?(:time) }
|
45
|
+
|
46
|
+
# If slice is used ignore
|
47
|
+
return nil if only_with_time.empty?
|
48
|
+
|
49
|
+
sorted = only_with_time.map(&:time).sort
|
50
|
+
humanize_time(sorted.first, sorted.last)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Replace TimeDifference with https://stackoverflow.com/a/4136485/1678507
|
54
|
+
def self.humanize_time(time_start, time_end, increments = 2)
|
55
|
+
miliseconds = (time_end - time_start) * 1000
|
56
|
+
|
57
|
+
list = [[1000, :ms], [60, :s], [60, :m], [24, :h]].map do |count, name|
|
58
|
+
next unless miliseconds.positive?
|
59
|
+
|
60
|
+
miliseconds, n = miliseconds.divmod(count)
|
61
|
+
|
62
|
+
"#{n.to_i}#{name}" unless n.to_i.zero?
|
63
|
+
end
|
64
|
+
|
65
|
+
list.compact.reverse[0..increments - 1].join(' ')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Filter Logic
|
69
|
+
# TODO: Simplify
|
70
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
71
|
+
def self.filter(data, flags = {}, args = {})
|
72
|
+
# Experimenting with deep clone
|
73
|
+
# results = Marshal.load(Marshal.dump(data))
|
74
|
+
results = data.clone
|
75
|
+
# results = data
|
76
|
+
|
77
|
+
results.select! do |row|
|
78
|
+
args.send(flags.logic || :all?) do |arg|
|
79
|
+
filter_row_key(row, arg, flags)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Ensure presecense of a specific field
|
84
|
+
results = filter_exists(results, flags[:exists]) if flags.key?(:exists)
|
85
|
+
|
86
|
+
# Time Zone
|
87
|
+
results = filter_modify_timezone(results, flags[:time_zone]) if flags.key?(:time_zone)
|
88
|
+
|
89
|
+
# Time Filtering
|
90
|
+
results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)
|
91
|
+
|
92
|
+
# Strip Results if Slice is defined
|
93
|
+
results = filter_slice(results, flags[:slice]) if flags.key?(:slice)
|
94
|
+
|
95
|
+
# Strip Results if Except is defined
|
96
|
+
results = filter_except(results, flags[:except]) if flags.key?(:except)
|
97
|
+
|
98
|
+
# Remove Blank from either slice or except
|
99
|
+
results.reject!(&:empty?)
|
100
|
+
|
101
|
+
# Sort
|
102
|
+
results.sort_by! { |x| x.slice(*flags[:sort]).values } if flags.key?(:sort)
|
103
|
+
|
104
|
+
# JSON Formatting
|
105
|
+
results = results.map { |x| Oj.dump(x) } if flags.key?(:json)
|
106
|
+
|
107
|
+
# Show Unique Only
|
108
|
+
results = filter_uniq(results, flags[:uniq]) if flags.key?(:uniq)
|
109
|
+
|
110
|
+
# Reverse
|
111
|
+
results.reverse! if flags[:reverse]
|
112
|
+
|
113
|
+
# Count occurrences / Skip Results
|
114
|
+
return filter_stats(results, flags) if flags.key?(:stats)
|
115
|
+
|
116
|
+
# Limit before Pluck / Flattening
|
117
|
+
results = filter_limit(results, flags[:limit]) if flags.key?(:limit)
|
118
|
+
|
119
|
+
# Pluck
|
120
|
+
results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)
|
121
|
+
|
122
|
+
results
|
123
|
+
end
|
124
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
125
|
+
|
126
|
+
# Limit / Ensure Exists and Valid Number
|
127
|
+
def self.filter_limit(results, limit)
|
128
|
+
return results unless limit.integer? && limit.positive?
|
129
|
+
|
130
|
+
results.shift limit
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.filter_modify_timezone(results, time_zone)
|
134
|
+
results.map do |x|
|
135
|
+
next unless x.key? :time
|
136
|
+
|
137
|
+
x = x.clone # Prevent Top Level Modification
|
138
|
+
x[:time] = x[:time].in_time_zone time_zone
|
139
|
+
|
140
|
+
x
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Filter Start and End Times
|
145
|
+
# TODO: This is a bit icky, simplify/dry
|
146
|
+
def self.filter_time(results, flags)
|
147
|
+
if flags.key?(:start)
|
148
|
+
begin
|
149
|
+
time_start = Time.parse(flags[:start])
|
150
|
+
|
151
|
+
results.select! do |x|
|
152
|
+
if x.time
|
153
|
+
time_start < x.time
|
154
|
+
else
|
155
|
+
true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
rescue StandardError
|
159
|
+
puts 'Unable to Process Start Time Filter'.pastel(:red)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if flags.key?(:end)
|
164
|
+
begin
|
165
|
+
time_start = Time.parse(flags[:end])
|
166
|
+
|
167
|
+
results.select! do |x|
|
168
|
+
if x.time
|
169
|
+
time_start > x.time
|
170
|
+
else
|
171
|
+
true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
rescue StandardError
|
175
|
+
puts 'Unable to Process End Time Filter'.pastel(:red)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
results
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.filter_except(results, except)
|
183
|
+
# Avoid Empty Results
|
184
|
+
if except.empty?
|
185
|
+
filter_empty_arg('except')
|
186
|
+
return results
|
187
|
+
end
|
188
|
+
|
189
|
+
results.map { |row| row.except(*except) }
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.filter_exists(results, exists)
|
193
|
+
# Avoid Empty Results
|
194
|
+
if exists.empty?
|
195
|
+
filter_empty_arg('exists')
|
196
|
+
return results
|
197
|
+
end
|
198
|
+
|
199
|
+
results.select { |row| (exists - row.keys).empty? }
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.filter_slice(results, slice)
|
203
|
+
# Avoid Empty Results
|
204
|
+
if slice.empty?
|
205
|
+
filter_empty_arg('slice')
|
206
|
+
return results
|
207
|
+
end
|
208
|
+
|
209
|
+
results.compact.map { |row| row.slice(*slice) }
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.filter_pluck(results, pluck)
|
213
|
+
# Avoid Empty Results
|
214
|
+
if pluck.empty?
|
215
|
+
filter_empty_arg('pluck')
|
216
|
+
return results
|
217
|
+
end
|
218
|
+
|
219
|
+
results.map { |x| x.slice(*pluck).values }.flatten
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.filter_uniq(results, unique)
|
223
|
+
# Avoid Empty Results
|
224
|
+
if unique.empty?
|
225
|
+
filter_empty_arg('uniq')
|
226
|
+
return results
|
227
|
+
end
|
228
|
+
|
229
|
+
unique.map do |field|
|
230
|
+
results.uniq { |x| x[field] }
|
231
|
+
end.inject(:&)
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.filter_stats(results, flags)
|
235
|
+
stats = flags[:stats]
|
236
|
+
|
237
|
+
# Avoid Empty Results
|
238
|
+
if stats.empty?
|
239
|
+
filter_empty_arg('stats')
|
240
|
+
return results
|
241
|
+
end
|
242
|
+
|
243
|
+
# Loop through Stats, Separate Hash/Tables
|
244
|
+
stats.map do |field|
|
245
|
+
occurrences = filter_count_occurrences(results, field, flags)
|
246
|
+
|
247
|
+
# Use Truncate For Long Keys
|
248
|
+
occurrences.transform_keys! { |key| key.to_s[0..flags[:truncate]] } if flags[:truncate]
|
249
|
+
|
250
|
+
# Total Occurences
|
251
|
+
total = occurrences.values.sum
|
252
|
+
|
253
|
+
# Percs
|
254
|
+
occurrences.transform_values! do |count|
|
255
|
+
[
|
256
|
+
count,
|
257
|
+
" #{percent(count, total)}%".pastel(:bright_black)
|
258
|
+
]
|
259
|
+
end
|
260
|
+
|
261
|
+
# Sort by total occurances / New Variable for Total
|
262
|
+
output = occurrences.sort_by(&:last).to_h.transform_values!(&:join).to_a
|
263
|
+
|
264
|
+
# Append Header / Total with field name
|
265
|
+
output.unshift([field.to_s.pastel(:bright_black), total])
|
266
|
+
|
267
|
+
# Format
|
268
|
+
output.to_h
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Percent Helper
|
273
|
+
def self.percent(value, total)
|
274
|
+
((value / total.to_f) * 100).round
|
275
|
+
end
|
276
|
+
|
277
|
+
# Helper to Count occurrences
|
278
|
+
def self.filter_count_occurrences(results, field, flags = {})
|
279
|
+
results.each_with_object(Hash.new(0)) do |entry, counts|
|
280
|
+
if entry.key? field
|
281
|
+
# Rounding in pagination breaks stats
|
282
|
+
key = if flags.key?(:round) && entry[field].numeric?
|
283
|
+
entry[field].to_f.round(flags.round)
|
284
|
+
else
|
285
|
+
entry[field]
|
286
|
+
end
|
287
|
+
|
288
|
+
counts[key] += 1
|
289
|
+
else
|
290
|
+
counts['None'.pastel(:bright_black)] += 1
|
291
|
+
end
|
292
|
+
|
293
|
+
counts
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def self.filter_empty_arg(arg)
|
298
|
+
puts [
|
299
|
+
'Ignoring'.pastel(:bright_yellow),
|
300
|
+
"--#{arg}".pastel(:cyan),
|
301
|
+
'it requires an argument'.pastel(:red)
|
302
|
+
].join(' ')
|
303
|
+
end
|
304
|
+
|
305
|
+
# Break out filter row logic into separate method
|
306
|
+
def self.filter_row_key(row, arg, flags)
|
307
|
+
return false if row.nil? # Nothing to filter if row empty
|
308
|
+
|
309
|
+
# Ignore Other Logic if Field isn't even included / Full Text Searching
|
310
|
+
return false unless row.key?(arg[:field]) || arg[:field] == :text
|
311
|
+
|
312
|
+
# Sensitivity Check / Check for Match / Full Text Searching
|
313
|
+
search_data = arg[:field] == :text ? row : row[arg.field]
|
314
|
+
match = filter_row_entry(search_data.to_s, arg, flags)
|
315
|
+
|
316
|
+
# Pivot of off include vs exclude
|
317
|
+
if arg.bang
|
318
|
+
!match
|
319
|
+
else
|
320
|
+
match
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Field Partial / Case / Exact search
|
325
|
+
def self.filter_row_entry(entry, arg, flags)
|
326
|
+
# Exact Matching / Unless doing full text search
|
327
|
+
return entry == arg.value.to_s if flags.key?(:exact) && arg.field != :text
|
328
|
+
|
329
|
+
# Cast to String/Integer Helper
|
330
|
+
entry, value = filter_entry_cast(entry, arg, flags)
|
331
|
+
|
332
|
+
entry.send(arg.logic, value)
|
333
|
+
end
|
334
|
+
|
335
|
+
# Handle casting to strings or integers
|
336
|
+
def self.filter_entry_cast(entry, arg, flags)
|
337
|
+
# Cast to String
|
338
|
+
value = arg.value.to_s
|
339
|
+
|
340
|
+
# No Logic on Empty Entries
|
341
|
+
return [entry, value] if entry.empty?
|
342
|
+
|
343
|
+
case arg.logic
|
344
|
+
when :include?
|
345
|
+
|
346
|
+
# Exact Case argument
|
347
|
+
unless flags.key?(:case)
|
348
|
+
entry = entry.downcase
|
349
|
+
value = value.downcase
|
350
|
+
end
|
351
|
+
when :>=, :<=
|
352
|
+
entry = entry.to_i if entry.numeric?
|
353
|
+
value = value.to_i if value&.numeric?
|
354
|
+
end
|
355
|
+
|
356
|
+
[entry, value]
|
357
|
+
end
|
358
|
+
|
359
|
+
def self.filter_entry_logic(entry, arg)
|
360
|
+
entry.send(arg.logic, arg.value)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Internal Query Helper
|
365
|
+
# query = 'gitlab-rails/application_json.log --message!="Cannot obtain an exclusive lease" --severity=error'
|
366
|
+
# Query.filter_internal(query)
|
367
|
+
def self.filter_internal(search = '')
|
368
|
+
files, flags, args = Args.parse(Shellwords.split(search))
|
369
|
+
flags[:combine] = true
|
370
|
+
|
371
|
+
# Default to everything
|
372
|
+
files = Thing.all.map(&:name) if files.empty?
|
373
|
+
|
374
|
+
Query.start(files, flags, args)
|
375
|
+
end
|
376
|
+
|
377
|
+
# rubocop:enable Metrics/ModuleLength
|
378
|
+
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,15 +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,
|
29
33
|
:exceptions_log, :gitaly_log, :free_m, :disk_free
|
30
34
|
|
31
35
|
# Find Needed Files for Report
|
32
36
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
33
|
-
def initialize(archive)
|
37
|
+
def initialize(archive, flags)
|
34
38
|
self.archive = archive
|
39
|
+
self.flags = flags
|
35
40
|
self.host = archive.things.find { |x| x.name == 'hostname' }
|
36
41
|
self.os_release = archive.things.find { |x| x.name == 'etc/os-release' }
|
37
42
|
self.selinux_status = archive.things.find { |x| x.name == 'sestatus' }
|
@@ -45,7 +50,6 @@ module GreenHat
|
|
45
50
|
self.gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
|
46
51
|
self.production_log = archive.things.find { |x| x.name == 'gitlab-rails/production_json.log' }
|
47
52
|
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
53
|
self.exceptions_log = archive.things.find { |x| x.name == 'gitlab-rails/exceptions_json.log' }
|
50
54
|
self.gitaly_log = archive.things.find { |x| x.name == 'gitaly/current' }
|
51
55
|
self.sidekiq_log = archive.things.find { |x| x.name == 'sidekiq/current' }
|
@@ -87,13 +91,16 @@ module GreenHat
|
|
87
91
|
output << 'GitLab'.pastel(:bright_yellow) if gitlab_manifest
|
88
92
|
output << gitlab_version if gitlab_manifest
|
89
93
|
output << gitlab_services if gitlab_status
|
90
|
-
output << title('Errors') if production_log || api_log ||
|
94
|
+
output << title('Errors') if production_log || api_log || sidekiq_log
|
91
95
|
output << production_errors if production_log
|
92
|
-
output << application_errors if
|
96
|
+
output << application_errors if archive.thing?('gitlab-rails/application_json.log')
|
93
97
|
output << sidekiq_errors if sidekiq_log
|
94
98
|
output << api_errors if api_log
|
95
99
|
output << exception_errors if exceptions_log
|
96
100
|
output << gitaly_errors if gitaly_log
|
101
|
+
output << workhorse_errors if archive.thing?('gitlab-workhorse/current')
|
102
|
+
|
103
|
+
full(output) if flags.full
|
97
104
|
|
98
105
|
# Final Space / Return
|
99
106
|
output << ''
|
@@ -101,6 +108,17 @@ module GreenHat
|
|
101
108
|
end
|
102
109
|
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
103
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
|
+
|
104
122
|
def exception_errors
|
105
123
|
count = exceptions_log.data.count
|
106
124
|
color = count.zero? ? :green : :red
|
@@ -111,6 +129,21 @@ module GreenHat
|
|
111
129
|
].join
|
112
130
|
end
|
113
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
|
+
|
114
147
|
def gitaly_errors
|
115
148
|
count = gitaly_log.data.count { |x| x.level == 'error' }
|
116
149
|
color = count.zero? ? :green : :red
|
@@ -145,7 +178,8 @@ module GreenHat
|
|
145
178
|
results = ShellHelper.filter_internal([
|
146
179
|
'gitlab-rails/application_json.log',
|
147
180
|
'--message!="Cannot obtain an exclusive lease"',
|
148
|
-
'--severity=error'
|
181
|
+
'--severity=error',
|
182
|
+
"--archive=#{archive.name}"
|
149
183
|
].join(' '))
|
150
184
|
|
151
185
|
count = results.count { |x| x&.severity == 'ERROR' }
|
@@ -200,6 +234,8 @@ module GreenHat
|
|
200
234
|
end
|
201
235
|
|
202
236
|
def selinux
|
237
|
+
return nil if selinux_status.data.nil?
|
238
|
+
|
203
239
|
status = selinux_status.data['SELinux status']
|
204
240
|
status_color = status == 'enabled' ? :green : :red
|
205
241
|
|
@@ -300,7 +336,7 @@ module GreenHat
|
|
300
336
|
title('Usage'),
|
301
337
|
' ['.pastel(:bright_black),
|
302
338
|
'='.pastel(:green) * (used / 2),
|
303
|
-
' ' * (50 - used / 2),
|
339
|
+
' ' * (50 - (used / 2)),
|
304
340
|
']'.pastel(:bright_black),
|
305
341
|
" #{100 - percent(free, total)}%".pastel(:green) # Inverse
|
306
342
|
].join
|
@@ -313,21 +349,37 @@ module GreenHat
|
|
313
349
|
|
314
350
|
formatted_mem = free_m.data.map { |x| GreenHat::Memory.memory_row x }
|
315
351
|
|
316
|
-
[
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
352
|
+
output = []
|
353
|
+
unless free.total.blank?
|
354
|
+
output << title('Total', :cyan, 14)
|
355
|
+
output << number_to_human_size(free.total.to_i * (1024**2))
|
356
|
+
output << "\n"
|
357
|
+
end
|
358
|
+
|
359
|
+
unless free.total.blank?
|
360
|
+
output << title('Used', :yellow, 14)
|
361
|
+
output << number_to_human_size(free.used.to_i * (1024**2))
|
362
|
+
output << "\n"
|
363
|
+
end
|
364
|
+
|
365
|
+
unless free.total.blank?
|
366
|
+
output << title('Free', :blue, 14)
|
367
|
+
output << number_to_human_size(free.free.to_i * (1024**2))
|
368
|
+
output << "\n"
|
369
|
+
end
|
370
|
+
|
371
|
+
unless free.total.blank?
|
372
|
+
output << title('Available', :green, 14)
|
373
|
+
output << number_to_human_size(free.available.to_i * (1024**2))
|
374
|
+
output << "\n"
|
375
|
+
end
|
376
|
+
|
377
|
+
output << "\n"
|
378
|
+
output << formatted_mem.map { |x| x.prepend ' ' * 2 }.join("\n")
|
379
|
+
|
380
|
+
output.join
|
381
|
+
rescue StandardError => e
|
382
|
+
LogBot.fatal('Memory', message: e.message, backtrace: e.backtrace.first)
|
331
383
|
end
|
332
384
|
|
333
385
|
def disks
|