greenhat 0.1.4
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 +7 -0
- data/README.md +64 -0
- data/bin/greenhat +12 -0
- data/lib/greenhat.rb +80 -0
- data/lib/greenhat/accessors/disk.rb +27 -0
- data/lib/greenhat/accessors/logs/production.rb +41 -0
- data/lib/greenhat/accessors/logs/sidekiq.rb +41 -0
- data/lib/greenhat/accessors/memory.rb +46 -0
- data/lib/greenhat/accessors/network.rb +8 -0
- data/lib/greenhat/accessors/process.rb +8 -0
- data/lib/greenhat/archive.rb +108 -0
- data/lib/greenhat/cli.rb +448 -0
- data/lib/greenhat/host.rb +182 -0
- data/lib/greenhat/logbot.rb +86 -0
- data/lib/greenhat/pry_helpers.rb +51 -0
- data/lib/greenhat/settings.rb +51 -0
- data/lib/greenhat/shell.rb +92 -0
- data/lib/greenhat/shell/cat.rb +125 -0
- data/lib/greenhat/shell/disk.rb +68 -0
- data/lib/greenhat/shell/faststats.rb +195 -0
- data/lib/greenhat/shell/gitlab.rb +45 -0
- data/lib/greenhat/shell/help.rb +15 -0
- data/lib/greenhat/shell/helper.rb +514 -0
- data/lib/greenhat/shell/log.rb +344 -0
- data/lib/greenhat/shell/memory.rb +31 -0
- data/lib/greenhat/shell/network.rb +12 -0
- data/lib/greenhat/shell/process.rb +12 -0
- data/lib/greenhat/shell/report.rb +319 -0
- data/lib/greenhat/thing.rb +121 -0
- data/lib/greenhat/thing/file_types.rb +705 -0
- data/lib/greenhat/thing/formatters/api_json.rb +34 -0
- data/lib/greenhat/thing/formatters/bracket_log.rb +44 -0
- data/lib/greenhat/thing/formatters/clean_raw.rb +23 -0
- data/lib/greenhat/thing/formatters/colon_split_strip.rb +12 -0
- data/lib/greenhat/thing/formatters/dotenv.rb +10 -0
- data/lib/greenhat/thing/formatters/format.rb +12 -0
- data/lib/greenhat/thing/formatters/free_m.rb +29 -0
- data/lib/greenhat/thing/formatters/gitlab_ctl_tail.rb +51 -0
- data/lib/greenhat/thing/formatters/gitlab_status.rb +26 -0
- data/lib/greenhat/thing/formatters/json.rb +63 -0
- data/lib/greenhat/thing/formatters/json_shellwords.rb +44 -0
- data/lib/greenhat/thing/formatters/multiline_json.rb +10 -0
- data/lib/greenhat/thing/formatters/raw.rb +18 -0
- data/lib/greenhat/thing/formatters/shellwords.rb +23 -0
- data/lib/greenhat/thing/formatters/table.rb +26 -0
- data/lib/greenhat/thing/formatters/time_json.rb +21 -0
- data/lib/greenhat/thing/formatters/time_shellwords.rb +28 -0
- data/lib/greenhat/thing/formatters/time_space.rb +36 -0
- data/lib/greenhat/thing/helpers.rb +71 -0
- data/lib/greenhat/thing/history.rb +51 -0
- data/lib/greenhat/thing/info_format.rb +193 -0
- data/lib/greenhat/thing/kind.rb +97 -0
- data/lib/greenhat/thing/spinner.rb +52 -0
- data/lib/greenhat/thing/super_log.rb +102 -0
- data/lib/greenhat/tty/custom_line.rb +29 -0
- data/lib/greenhat/tty/line.rb +326 -0
- data/lib/greenhat/tty/reader.rb +110 -0
- data/lib/greenhat/version.rb +3 -0
- data/lib/greenhat/views/css.slim +126 -0
- data/lib/greenhat/views/disk_free.slim +18 -0
- data/lib/greenhat/views/index.slim +51 -0
- data/lib/greenhat/views/info.slim +39 -0
- data/lib/greenhat/views/manifest.slim +22 -0
- data/lib/greenhat/views/memory.slim +18 -0
- data/lib/greenhat/views/netstat.slim +20 -0
- data/lib/greenhat/views/process.slim +21 -0
- data/lib/greenhat/views/systemctl.slim +40 -0
- data/lib/greenhat/views/ulimit.slim +15 -0
- data/lib/greenhat/web.rb +46 -0
- metadata +476 -0
@@ -0,0 +1,344 @@
|
|
1
|
+
module GreenHat
|
2
|
+
# CLI Helper
|
3
|
+
module Shell
|
4
|
+
# Logs
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
6
|
+
module Log
|
7
|
+
def self.help
|
8
|
+
puts "\u2500".colorize(:cyan) * 20
|
9
|
+
puts "#{'Logs'.colorize(:yellow)} find stuff"
|
10
|
+
puts "\u2500".colorize(:cyan) * 20
|
11
|
+
|
12
|
+
puts 'Command Summary'.colorize(:blue)
|
13
|
+
puts ' show'.colorize(:green)
|
14
|
+
puts ' Just print selected logs'
|
15
|
+
puts ' filter'.colorize(:green)
|
16
|
+
puts ' Key/Field Filtering'
|
17
|
+
puts ' search'.colorize(:green)
|
18
|
+
puts ' General text by entry searching'
|
19
|
+
puts ' ls'.colorize(:green)
|
20
|
+
puts ' List available files'
|
21
|
+
puts
|
22
|
+
|
23
|
+
filter_help
|
24
|
+
|
25
|
+
puts
|
26
|
+
search_help
|
27
|
+
end
|
28
|
+
|
29
|
+
# List Files Helpers
|
30
|
+
def self.list(args = [])
|
31
|
+
all = false
|
32
|
+
all = true if args.include?('-a') || args.include?('--all')
|
33
|
+
|
34
|
+
files = ShellHelper::Log.list
|
35
|
+
|
36
|
+
# Sort
|
37
|
+
files.sort_by!(&:name)
|
38
|
+
|
39
|
+
# Short & Uniq
|
40
|
+
files.uniq!(&:name) unless all
|
41
|
+
|
42
|
+
# Print
|
43
|
+
files.each do |log|
|
44
|
+
if all
|
45
|
+
puts "- #{log.friendly_name}"
|
46
|
+
else
|
47
|
+
puts "- #{log.name.colorize(:yellow)}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.ls(args = [])
|
53
|
+
list(args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.show(log_list)
|
57
|
+
# Prepare Log List
|
58
|
+
log_list = ShellHelper.prepare_list(log_list)
|
59
|
+
|
60
|
+
# Convert to Things
|
61
|
+
logs = ShellHelper.find_things(log_list)
|
62
|
+
|
63
|
+
logs.map!(&:data)
|
64
|
+
|
65
|
+
ShellHelper.show logs.flatten
|
66
|
+
end
|
67
|
+
|
68
|
+
# ========================================================================
|
69
|
+
# Filter
|
70
|
+
# ========================================================================
|
71
|
+
# Supported Params
|
72
|
+
# --or (Filter OR instead of AND)
|
73
|
+
# --total Total Count Entries Only
|
74
|
+
# --project=thingy --exclude_this!=asdf *
|
75
|
+
# --slice: only grab specific fields --slice=path (slice multiple with comma)
|
76
|
+
# --slice=time,path (E.g. log filter --path='mirror/pull' --slice=path,time )
|
77
|
+
# --except: Exclude specific fields (except multiple with comma)
|
78
|
+
# Example: log filter --path='mirror/pull' --except=params
|
79
|
+
def self.default(raw_list)
|
80
|
+
filter(raw_list)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.filter(raw_list)
|
84
|
+
# Print Helper
|
85
|
+
if raw_list == ['help']
|
86
|
+
filter_help
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
|
90
|
+
# Extract Args
|
91
|
+
log_list, opts, args = ShellHelper.param_parse(raw_list)
|
92
|
+
|
93
|
+
# Prepare Log List
|
94
|
+
log_list = ShellHelper.prepare_list(log_list)
|
95
|
+
|
96
|
+
# AND / OR Filtering
|
97
|
+
filter_type = args.or ? :any? : :all?
|
98
|
+
|
99
|
+
results = ShellHelper.filter_start(log_list, filter_type, args, opts)
|
100
|
+
|
101
|
+
# Skipo and Print Total if set
|
102
|
+
if args.total
|
103
|
+
ShellHelper.total_count(results)
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check Search Results
|
108
|
+
if results.instance_of?(Hash) && results.values.flatten.empty?
|
109
|
+
puts 'No results'.colorize(:red)
|
110
|
+
else
|
111
|
+
# This causes the key 'colorized' output to also be included
|
112
|
+
ShellHelper.show(results.to_a.compact.flatten, args)
|
113
|
+
end
|
114
|
+
|
115
|
+
# log filter --path='cloud/gitlab-automation' --path='/pull' --all
|
116
|
+
# cloud/gitlab-automation
|
117
|
+
# log filter --project=thingy --other_filter=asdf *
|
118
|
+
rescue StandardError => e
|
119
|
+
binding.pry
|
120
|
+
LogBot.fatal('Filter', message: e.message, backtrace: e.backtrace.first)
|
121
|
+
end
|
122
|
+
# ========================================================================
|
123
|
+
|
124
|
+
# rubocop:disable Metrics/MethodLength
|
125
|
+
def self.filter_help
|
126
|
+
puts "\u2500".colorize(:cyan) * 20
|
127
|
+
puts 'Log Filter'.colorize(:yellow)
|
128
|
+
puts "\u2500".colorize(:cyan) * 20
|
129
|
+
|
130
|
+
puts 'Options'.colorize(:blue)
|
131
|
+
puts '--raw'.colorize(:green)
|
132
|
+
puts ' Do not use less/paging'
|
133
|
+
puts
|
134
|
+
|
135
|
+
puts '--round'.colorize(:green)
|
136
|
+
puts ' Attempt to round all integers. Default: 2.'
|
137
|
+
puts ' E.g. --round, --round=3, --round=0'
|
138
|
+
puts
|
139
|
+
|
140
|
+
puts '--json'.colorize(:green)
|
141
|
+
puts ' Print output back into JSON'
|
142
|
+
puts
|
143
|
+
|
144
|
+
puts '--or'.colorize(:green)
|
145
|
+
puts ' Filters will use OR instead of AND'
|
146
|
+
puts
|
147
|
+
|
148
|
+
puts '--total'.colorize(:green)
|
149
|
+
puts ' Print only total count of matching entries'
|
150
|
+
puts
|
151
|
+
|
152
|
+
puts '--slice'.colorize(:green)
|
153
|
+
puts ' Extract specific fields from entries (slice multiple with comma)'
|
154
|
+
puts ' Ex: --slice=path or --slice=path,params'
|
155
|
+
puts
|
156
|
+
|
157
|
+
puts '--except'.colorize(:green)
|
158
|
+
puts ' Exclude specific fields (except multiple with comma)'
|
159
|
+
puts ' Ex: --except=params --except=params,path'
|
160
|
+
puts
|
161
|
+
|
162
|
+
puts '--stats'.colorize(:green)
|
163
|
+
puts ' Order/Count occurrances by field'
|
164
|
+
puts ' Ex: --stats=params --except=params,path'
|
165
|
+
puts
|
166
|
+
|
167
|
+
puts '--uniq'.colorize(:green)
|
168
|
+
puts ' Show unique values only'
|
169
|
+
puts ' Ex: --uniq=params --uniq=params,path'
|
170
|
+
puts
|
171
|
+
|
172
|
+
puts '--pluck'.colorize(:green)
|
173
|
+
puts ' Extract values from entries'
|
174
|
+
puts ' Ex: --pluck=params --pluck=params,path'
|
175
|
+
puts
|
176
|
+
|
177
|
+
puts '--archive'.colorize(:green)
|
178
|
+
puts ' Limit to specific archvie name (inclusive). Matching SOS tar.gz name'
|
179
|
+
puts ' Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
|
180
|
+
puts
|
181
|
+
|
182
|
+
puts '--sort'.colorize(:green)
|
183
|
+
puts ' Sort by multiple fields'
|
184
|
+
puts ' Ex: --sort=duration_s,db_duration_s'
|
185
|
+
puts
|
186
|
+
|
187
|
+
puts '--reverse'.colorize(:green)
|
188
|
+
puts ' Reverse all results'
|
189
|
+
puts ' Ex: --reverse'
|
190
|
+
puts
|
191
|
+
|
192
|
+
puts '--combine'.colorize(:green)
|
193
|
+
puts ' Omit archive identifier dividers. Useful with sort or time filters'
|
194
|
+
puts ' Ex: --combine'
|
195
|
+
puts
|
196
|
+
|
197
|
+
puts '--start'.colorize(:green)
|
198
|
+
puts ' Show events after specified time. Filtered by the `time` field'
|
199
|
+
puts ' Use with `--end` for between selections'
|
200
|
+
puts ' Ex: log filter --start="2021-06-22 14:44 UTC" --end="2021-06-22 14:45 UTC"'
|
201
|
+
puts
|
202
|
+
|
203
|
+
puts '--end'.colorize(:green)
|
204
|
+
puts ' Show events before specified time. Filtered by the `time` field'
|
205
|
+
puts ' Use with `--start` for between selections'
|
206
|
+
puts ' Ex: log filter --end="2021-06-22"'
|
207
|
+
puts
|
208
|
+
|
209
|
+
puts 'Field Searching'.colorize(:blue)
|
210
|
+
puts ' --[key]=[value]'
|
211
|
+
puts ' Search in key for value'
|
212
|
+
puts ' Example: --path=mirror/pull'
|
213
|
+
puts
|
214
|
+
|
215
|
+
puts 'Search specific logs'.colorize(:blue)
|
216
|
+
puts ' Any non dash parameters will be the log list to search from'
|
217
|
+
puts " Ex: log filter --path=api sidekiq/current (hint: use `#{'ls'.colorize(:yellow)}` for log names"
|
218
|
+
puts
|
219
|
+
|
220
|
+
puts 'Example Queries'.colorize(:blue)
|
221
|
+
puts " Also see #{'filter_examples'.colorize(:light_blue)} for even more examples"
|
222
|
+
puts 'log filter --class=BuildFinishedWorker sidekiq/current --slice=time,message'
|
223
|
+
puts 'log filter gitlab-rails/api_json.log --slice=ua --uniq=ua --ua=gitlab-runner'
|
224
|
+
|
225
|
+
puts
|
226
|
+
end
|
227
|
+
# rubocop:enable Metrics/MethodLength
|
228
|
+
|
229
|
+
# rubocop:disable Layout/LineLength
|
230
|
+
# TODO: Add a lot more examples
|
231
|
+
def self.filter_examples
|
232
|
+
puts 'log filter sidekiq/current --sort=db_duration_s --job_status=done --sort=duration_s,db_duration_s --slice=duration_s,db_duration_s --reverse'
|
233
|
+
end
|
234
|
+
# rubocop:enable Layout/LineLength
|
235
|
+
|
236
|
+
# ========================================================================
|
237
|
+
# Search (Full Text / String Search)
|
238
|
+
# ========================================================================
|
239
|
+
# Supported Params
|
240
|
+
# --text='asdf'
|
241
|
+
# --text!='asdf'
|
242
|
+
# --regex='' # TODO?
|
243
|
+
# --slice=time,path (E.g. log filter --path='mirror/pull' --slice=path,time )
|
244
|
+
# --except: Exclude specific fields (except multiple with comma)
|
245
|
+
|
246
|
+
# --total Total Count Entries Only
|
247
|
+
def self.search(initial_param)
|
248
|
+
# Extract Args
|
249
|
+
log_list, opts, args = ShellHelper.param_parse(initial_param)
|
250
|
+
|
251
|
+
# Prepare Log List
|
252
|
+
log_list = ShellHelper.prepare_list(log_list)
|
253
|
+
|
254
|
+
# AND / OR Filtering
|
255
|
+
filter_type = args.or ? :any? : :all?
|
256
|
+
|
257
|
+
results = ShellHelper.search_start(log_list, filter_type, args, opts)
|
258
|
+
|
259
|
+
# Skipo and Print Total if set
|
260
|
+
if args.total
|
261
|
+
ShellHelper.total_count(results)
|
262
|
+
return true
|
263
|
+
end
|
264
|
+
|
265
|
+
# Check Search Results
|
266
|
+
if results.values.flatten.empty?
|
267
|
+
puts 'No results'.colorize(:red)
|
268
|
+
else
|
269
|
+
# This causes the key 'colorized' output to also be included
|
270
|
+
ShellHelper.show(results.to_a.compact.flatten, args)
|
271
|
+
end
|
272
|
+
rescue StandardError => e
|
273
|
+
LogBot.fatal('Filter', message: e.message, backtrace: e.backtrace.first)
|
274
|
+
end
|
275
|
+
# ========================================================================
|
276
|
+
|
277
|
+
# Supported Params
|
278
|
+
# --text='asdf'
|
279
|
+
# --text!='asdf'
|
280
|
+
# --slice=time,path (E.g. log filter --path='mirror/pull' --slice=path,time )
|
281
|
+
# --except: Exclude specific fields (except multiple with comma)
|
282
|
+
|
283
|
+
# rubocop:disable Metrics/MethodLength
|
284
|
+
def self.search_help
|
285
|
+
puts "\u2500".colorize(:cyan) * 20
|
286
|
+
puts 'Log Search'.colorize(:yellow)
|
287
|
+
puts "\u2500".colorize(:cyan) * 20
|
288
|
+
|
289
|
+
puts 'Search will do a full line include or exclude text search'
|
290
|
+
|
291
|
+
puts 'Options'.colorize(:blue)
|
292
|
+
puts '--text'.colorize(:green)
|
293
|
+
puts ' Primary parameter for searching. Include or ! to exclude'
|
294
|
+
puts ' Ex: --text=BuildHooksWorker --text!=start sidekiq/current'
|
295
|
+
puts
|
296
|
+
|
297
|
+
puts '--total'.colorize(:green)
|
298
|
+
puts ' Print only total count of matching entries'
|
299
|
+
puts
|
300
|
+
|
301
|
+
puts '--slice'.colorize(:green)
|
302
|
+
puts ' Extract specific fields from entries (slice multiple with comma)'
|
303
|
+
puts ' Ex: --slice=path or --slice=path,params'
|
304
|
+
puts
|
305
|
+
|
306
|
+
puts '--except'.colorize(:green)
|
307
|
+
puts ' Exclude specific fields (except multiple with comma)'
|
308
|
+
puts ' Ex: --except=params --except=params,path'
|
309
|
+
puts
|
310
|
+
|
311
|
+
puts '--archive'.colorize(:green)
|
312
|
+
puts ' Limit to specific archvie name (inclusive). Matching SOS tar.gz name'
|
313
|
+
puts ' Ex: --archive=dev-gitlab_20210622154626, --archive=202106,202107'
|
314
|
+
puts
|
315
|
+
|
316
|
+
puts 'Search specific logs'.colorize(:blue)
|
317
|
+
puts ' Any non dash parameters will be the log list to search from'
|
318
|
+
puts " Ex: log filter --path=api sidekiq/current (hint: use `#{'ls'.colorize(:yellow)}` for log names"
|
319
|
+
puts
|
320
|
+
|
321
|
+
puts 'Example Queries'.colorize(:blue)
|
322
|
+
puts 'log search --text=BuildHooksWorker --text!=start sidekiq/current --total'
|
323
|
+
puts 'log search --text=BuildHooksWorker --text!=start --slice=enqueued_at sidekiq/current'
|
324
|
+
puts
|
325
|
+
end
|
326
|
+
# rubocop:enable Metrics/MethodLength
|
327
|
+
|
328
|
+
# ------------------------------------------------------------------------
|
329
|
+
end
|
330
|
+
# rubocop:enable Metrics/ModuleLength
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
module GreenHat
|
335
|
+
module ShellHelper
|
336
|
+
# Log Helpers
|
337
|
+
module Log
|
338
|
+
def self.list
|
339
|
+
Thing.all.select(&:log)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
# --------
|
343
|
+
end
|
344
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module GreenHat
|
2
|
+
# CLI Helper
|
3
|
+
module Shell
|
4
|
+
# Logs
|
5
|
+
module Memory
|
6
|
+
# Easy Show All
|
7
|
+
def self.free
|
8
|
+
ShellHelper.file_output GreenHat::Memory.free
|
9
|
+
end
|
10
|
+
|
11
|
+
# Pretty Show
|
12
|
+
def self.show
|
13
|
+
results = ShellHelper.file_process(GreenHat::Memory.free) do |file|
|
14
|
+
list = [
|
15
|
+
file.friendly_name
|
16
|
+
]
|
17
|
+
|
18
|
+
file.data.each do |mem|
|
19
|
+
list.push GreenHat::Memory.memory_row mem
|
20
|
+
end
|
21
|
+
|
22
|
+
list.push "\n"
|
23
|
+
|
24
|
+
list
|
25
|
+
end
|
26
|
+
|
27
|
+
ShellHelper.show(results.flatten)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
module GreenHat
|
2
|
+
# Root Level Shell / Report Helper
|
3
|
+
module Shell
|
4
|
+
def self.report(raw)
|
5
|
+
args = {
|
6
|
+
raw: raw.include?('--raw')
|
7
|
+
}
|
8
|
+
|
9
|
+
ShellHelper.show(Archive.all.map(&:report).map(&:show).flatten, args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module GreenHat
|
15
|
+
# Report Generator Helper
|
16
|
+
# rubocop:disable Metrics/ClassLength
|
17
|
+
class Report
|
18
|
+
include ActionView::Helpers::NumberHelper
|
19
|
+
|
20
|
+
attr_accessor :archive, :host, :os_release, :selinux_status, :cpu, :uname,
|
21
|
+
:timedatectl, :uptime, :meminfo, :gitlab_manifest, :gitlab_status,
|
22
|
+
:production_log, :api_log, :application_log, :sidekiq_log, :free_m
|
23
|
+
|
24
|
+
# Find Needed Files for Report
|
25
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
26
|
+
def initialize(archive)
|
27
|
+
self.archive = archive
|
28
|
+
self.host = archive.things.find { |x| x.name == 'hostname' }
|
29
|
+
self.os_release = archive.things.find { |x| x.name == 'etc/os-release' }
|
30
|
+
self.selinux_status = archive.things.find { |x| x.name == 'sestatus' }
|
31
|
+
self.cpu = archive.things.find { |x| x.name == 'lscpu' }
|
32
|
+
self.uname = archive.things.find { |x| x.name == 'uname' }
|
33
|
+
self.timedatectl = archive.things.find { |x| x.name == 'timedatectl' }
|
34
|
+
self.uptime = archive.things.find { |x| x.name == 'uptime' }
|
35
|
+
self.meminfo = archive.things.find { |x| x.name == 'meminfo' }
|
36
|
+
self.free_m = archive.things.find { |x| x.name == 'free_m' }
|
37
|
+
self.gitlab_manifest = archive.things.find { |x| x.name == 'gitlab/version-manifest.json' }
|
38
|
+
self.gitlab_status = archive.things.find { |x| x.name == 'gitlab_status' }
|
39
|
+
self.production_log = archive.things.find { |x| x.name == 'gitlab-rails/production_json.log' }
|
40
|
+
self.api_log = archive.things.find { |x| x.name == 'gitlab-rails/api_json.log' }
|
41
|
+
self.application_log = archive.things.find { |x| x.name == 'gitlab-rails/application_json.log' }
|
42
|
+
self.sidekiq_log = archive.things.find { |x| x.name == 'sidekiq/current' }
|
43
|
+
end
|
44
|
+
|
45
|
+
def show
|
46
|
+
output = [
|
47
|
+
archive.friendly_name.colorize(:blue)
|
48
|
+
]
|
49
|
+
|
50
|
+
# OS
|
51
|
+
output << 'OS'.colorize(:light_yellow)
|
52
|
+
output << hostname if host
|
53
|
+
output << distro if os_release
|
54
|
+
output << selinux if selinux_status
|
55
|
+
# output << arch if cpu
|
56
|
+
output << kernel if uname
|
57
|
+
output << sys_time if timedatectl
|
58
|
+
output << sys_uptime if uptime
|
59
|
+
output << load_average if uptime && cpu
|
60
|
+
output << ''
|
61
|
+
|
62
|
+
# Memory
|
63
|
+
if meminfo || free_m
|
64
|
+
output << 'Memory'.colorize(:light_yellow)
|
65
|
+
output << memory if meminfo
|
66
|
+
output << memory_free if free_m
|
67
|
+
output << ''
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gitlab
|
71
|
+
output << 'GitLab'.colorize(:light_yellow) if gitlab_manifest
|
72
|
+
output << gitlab_version if gitlab_manifest
|
73
|
+
output << gitlab_services if gitlab_status
|
74
|
+
output << title('Errors') if production_log || api_log || application_log || sidekiq_log
|
75
|
+
output << production_errors if production_log
|
76
|
+
output << api_errors if api_log
|
77
|
+
output << application_errors if application_log
|
78
|
+
output << sidekiq_errors if sidekiq_log
|
79
|
+
|
80
|
+
# Final Space / Return
|
81
|
+
output << ''
|
82
|
+
output
|
83
|
+
end
|
84
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
85
|
+
|
86
|
+
def production_errors
|
87
|
+
count = production_log.data.count { |x| x.status == 500 }
|
88
|
+
color = count.zero? ? :green : :red
|
89
|
+
|
90
|
+
[
|
91
|
+
title(' Production', :light_red, 18),
|
92
|
+
count.to_s.colorize(color)
|
93
|
+
].join
|
94
|
+
end
|
95
|
+
|
96
|
+
def api_errors
|
97
|
+
count = api_log.data.count { |x| x.status == 500 }
|
98
|
+
color = count.zero? ? :green : :red
|
99
|
+
|
100
|
+
[
|
101
|
+
title(' API', :light_red, 18),
|
102
|
+
count.to_s.colorize(color)
|
103
|
+
].join
|
104
|
+
end
|
105
|
+
|
106
|
+
def application_errors
|
107
|
+
count = application_log.data.count { |x| x&.severity == 'ERROR' }
|
108
|
+
color = count.zero? ? :green : :red
|
109
|
+
|
110
|
+
[
|
111
|
+
title(' Application', :light_red, 18),
|
112
|
+
count.to_s.colorize(color)
|
113
|
+
].join
|
114
|
+
end
|
115
|
+
|
116
|
+
def sidekiq_errors
|
117
|
+
count = sidekiq_log.data.count { |x| x&.severity == 'ERROR' }
|
118
|
+
color = count.zero? ? :green : :red
|
119
|
+
|
120
|
+
[
|
121
|
+
title(' Sidekiq', :light_red, 18),
|
122
|
+
count.to_s.colorize(color)
|
123
|
+
].join
|
124
|
+
end
|
125
|
+
|
126
|
+
def gitlab_services
|
127
|
+
list = gitlab_status.data.keys.sort.map do |service|
|
128
|
+
color = gitlab_status.data.dig(service, 0, :status) == 'run' ? :green : :red
|
129
|
+
service.colorize(color)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Keep Alphabetical Sort
|
133
|
+
groups = list.each_slice((list.size / 3.to_f).round).to_a
|
134
|
+
|
135
|
+
table = TTY::Table.new do |t|
|
136
|
+
loop do
|
137
|
+
break if groups.all?(&:empty?)
|
138
|
+
|
139
|
+
t << groups.map(&:shift)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
[
|
144
|
+
title('Services'),
|
145
|
+
"\n ",
|
146
|
+
table.render(:unicode, padding: [0, 1, 0, 1], indent: 3)
|
147
|
+
].join
|
148
|
+
rescue StandardError => e
|
149
|
+
LogBot.fatal('GitLab Services', message: e.message, backtrace: e.backtrace.first)
|
150
|
+
end
|
151
|
+
|
152
|
+
def gitlab_version
|
153
|
+
[
|
154
|
+
title('Version'),
|
155
|
+
gitlab_manifest.data.build_version
|
156
|
+
].join
|
157
|
+
end
|
158
|
+
|
159
|
+
def hostname
|
160
|
+
[
|
161
|
+
title('Hostname'),
|
162
|
+
host.data.first
|
163
|
+
].join
|
164
|
+
end
|
165
|
+
|
166
|
+
def distro
|
167
|
+
[
|
168
|
+
title('Distro'),
|
169
|
+
"[#{os_release.data.ID}] ".colorize(:light_black),
|
170
|
+
os_release.data.PRETTY_NAME
|
171
|
+
].join
|
172
|
+
end
|
173
|
+
|
174
|
+
def selinux
|
175
|
+
status = selinux_status.data['SELinux status']
|
176
|
+
status_color = status == 'enabled' ? :green : :red
|
177
|
+
|
178
|
+
[
|
179
|
+
title('SeLinux'),
|
180
|
+
status.colorize(status_color),
|
181
|
+
' (',
|
182
|
+
selinux_status.data['Current mode'],
|
183
|
+
')'
|
184
|
+
].join
|
185
|
+
end
|
186
|
+
|
187
|
+
def arch
|
188
|
+
[
|
189
|
+
title('Arch'),
|
190
|
+
cpu.data.Architecture
|
191
|
+
].join
|
192
|
+
end
|
193
|
+
|
194
|
+
def kernel
|
195
|
+
# TODO: Better way to consistently get uname info?
|
196
|
+
value, build = uname.data.first.split[2].split('-')
|
197
|
+
[
|
198
|
+
title('Kernel'),
|
199
|
+
value,
|
200
|
+
" (#{build})".colorize(:light_black)
|
201
|
+
].join
|
202
|
+
end
|
203
|
+
|
204
|
+
# Helper for finding if NTP is enabled
|
205
|
+
def ntp_keys
|
206
|
+
[
|
207
|
+
'Network time on', 'NTP enabled', 'NTP service', 'System clock synchronized'
|
208
|
+
]
|
209
|
+
end
|
210
|
+
|
211
|
+
def sys_time
|
212
|
+
ntp_statuses = timedatectl.data.slice(*ntp_keys).values.compact
|
213
|
+
|
214
|
+
enabled = %w[active yes] & ntp_statuses
|
215
|
+
ntp_status = ntp_statuses.first
|
216
|
+
ntp_color = enabled.empty? ? :red : :green
|
217
|
+
|
218
|
+
# Fall Back
|
219
|
+
ntp_status ||= 'unknown'
|
220
|
+
|
221
|
+
[
|
222
|
+
title('Sys Time'),
|
223
|
+
timedatectl.data['Local time'],
|
224
|
+
' (ntp: '.colorize(:light_black),
|
225
|
+
ntp_status.colorize(ntp_color),
|
226
|
+
')'.colorize(:light_black)
|
227
|
+
].join
|
228
|
+
end
|
229
|
+
|
230
|
+
# Strip/Simplify Uptime
|
231
|
+
def sys_uptime
|
232
|
+
init = uptime.data.first.split(', load average').first.strip
|
233
|
+
|
234
|
+
[
|
235
|
+
title('Uptime'),
|
236
|
+
init.split('up ', 2).last
|
237
|
+
].join
|
238
|
+
end
|
239
|
+
|
240
|
+
def load_average
|
241
|
+
cpu_count = cpu.data['CPU(s)'].to_i
|
242
|
+
intervals = uptime.data.first.split('load average: ', 2).last.split(', ').map(&:to_f)
|
243
|
+
|
244
|
+
# Generate Colorized Text for Output
|
245
|
+
intervals_text = intervals.map do |interval|
|
246
|
+
value = percent(interval, cpu_count)
|
247
|
+
color = value > 100 ? :red : :green
|
248
|
+
[
|
249
|
+
interval,
|
250
|
+
' (',
|
251
|
+
"#{value}%".colorize(color),
|
252
|
+
')'
|
253
|
+
].join
|
254
|
+
end
|
255
|
+
|
256
|
+
[
|
257
|
+
title('LoadAvg'),
|
258
|
+
"[CPU #{cpu_count}] ".colorize(:light_white),
|
259
|
+
intervals_text.join(', ')
|
260
|
+
].join
|
261
|
+
end
|
262
|
+
|
263
|
+
def memory
|
264
|
+
total = ShellHelper.human_size_to_number(meminfo.data['MemTotal'])
|
265
|
+
free = ShellHelper.human_size_to_number(meminfo.data['MemFree'])
|
266
|
+
used = percent((total - free), total)
|
267
|
+
[
|
268
|
+
title('MemUsed'),
|
269
|
+
'['.colorize(:light_black),
|
270
|
+
'='.colorize(:green) * (used / 2),
|
271
|
+
' ' * (50 - used / 2),
|
272
|
+
']'.colorize(:light_black),
|
273
|
+
" #{100 - percent(free, total)}%".colorize(:green), # Inverse
|
274
|
+
"\n",
|
275
|
+
title('Total'),
|
276
|
+
number_to_human_size(total).colorize(:light_white),
|
277
|
+
"\n",
|
278
|
+
title('Used'),
|
279
|
+
number_to_human_size(total - free),
|
280
|
+
"\n",
|
281
|
+
title('Free'),
|
282
|
+
number_to_human_size(free)
|
283
|
+
].join
|
284
|
+
end
|
285
|
+
|
286
|
+
def memory_free
|
287
|
+
free = free_m.data.find { |x| x.kind == 'Mem' }
|
288
|
+
|
289
|
+
return unless free
|
290
|
+
|
291
|
+
[
|
292
|
+
title('Total', :cyan, 14),
|
293
|
+
number_to_human_size(free.total.to_i * 1024**2),
|
294
|
+
"\n",
|
295
|
+
title('Used', :yellow, 14),
|
296
|
+
number_to_human_size(free.used.to_i * 1024**2),
|
297
|
+
"\n",
|
298
|
+
title('Free', :blue, 14),
|
299
|
+
number_to_human_size(free.free.to_i * 1024**2),
|
300
|
+
"\n",
|
301
|
+
title('Available', :green, 14),
|
302
|
+
number_to_human_size(free.available.to_i * 1024**2)
|
303
|
+
].join
|
304
|
+
end
|
305
|
+
|
306
|
+
# ----------------------------
|
307
|
+
# Helpers
|
308
|
+
# ----------------------------
|
309
|
+
def percent(value, total)
|
310
|
+
((value / total.to_f) * 100).round
|
311
|
+
end
|
312
|
+
|
313
|
+
# Helper to Make Cyan Titles
|
314
|
+
def title(name, color = :cyan, ljust = 12)
|
315
|
+
" #{name}:".ljust(ljust).colorize(color)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
# rubocop:enable Metrics/ClassLength
|
319
|
+
end
|