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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +64 -0
  3. data/bin/greenhat +12 -0
  4. data/lib/greenhat.rb +80 -0
  5. data/lib/greenhat/accessors/disk.rb +27 -0
  6. data/lib/greenhat/accessors/logs/production.rb +41 -0
  7. data/lib/greenhat/accessors/logs/sidekiq.rb +41 -0
  8. data/lib/greenhat/accessors/memory.rb +46 -0
  9. data/lib/greenhat/accessors/network.rb +8 -0
  10. data/lib/greenhat/accessors/process.rb +8 -0
  11. data/lib/greenhat/archive.rb +108 -0
  12. data/lib/greenhat/cli.rb +448 -0
  13. data/lib/greenhat/host.rb +182 -0
  14. data/lib/greenhat/logbot.rb +86 -0
  15. data/lib/greenhat/pry_helpers.rb +51 -0
  16. data/lib/greenhat/settings.rb +51 -0
  17. data/lib/greenhat/shell.rb +92 -0
  18. data/lib/greenhat/shell/cat.rb +125 -0
  19. data/lib/greenhat/shell/disk.rb +68 -0
  20. data/lib/greenhat/shell/faststats.rb +195 -0
  21. data/lib/greenhat/shell/gitlab.rb +45 -0
  22. data/lib/greenhat/shell/help.rb +15 -0
  23. data/lib/greenhat/shell/helper.rb +514 -0
  24. data/lib/greenhat/shell/log.rb +344 -0
  25. data/lib/greenhat/shell/memory.rb +31 -0
  26. data/lib/greenhat/shell/network.rb +12 -0
  27. data/lib/greenhat/shell/process.rb +12 -0
  28. data/lib/greenhat/shell/report.rb +319 -0
  29. data/lib/greenhat/thing.rb +121 -0
  30. data/lib/greenhat/thing/file_types.rb +705 -0
  31. data/lib/greenhat/thing/formatters/api_json.rb +34 -0
  32. data/lib/greenhat/thing/formatters/bracket_log.rb +44 -0
  33. data/lib/greenhat/thing/formatters/clean_raw.rb +23 -0
  34. data/lib/greenhat/thing/formatters/colon_split_strip.rb +12 -0
  35. data/lib/greenhat/thing/formatters/dotenv.rb +10 -0
  36. data/lib/greenhat/thing/formatters/format.rb +12 -0
  37. data/lib/greenhat/thing/formatters/free_m.rb +29 -0
  38. data/lib/greenhat/thing/formatters/gitlab_ctl_tail.rb +51 -0
  39. data/lib/greenhat/thing/formatters/gitlab_status.rb +26 -0
  40. data/lib/greenhat/thing/formatters/json.rb +63 -0
  41. data/lib/greenhat/thing/formatters/json_shellwords.rb +44 -0
  42. data/lib/greenhat/thing/formatters/multiline_json.rb +10 -0
  43. data/lib/greenhat/thing/formatters/raw.rb +18 -0
  44. data/lib/greenhat/thing/formatters/shellwords.rb +23 -0
  45. data/lib/greenhat/thing/formatters/table.rb +26 -0
  46. data/lib/greenhat/thing/formatters/time_json.rb +21 -0
  47. data/lib/greenhat/thing/formatters/time_shellwords.rb +28 -0
  48. data/lib/greenhat/thing/formatters/time_space.rb +36 -0
  49. data/lib/greenhat/thing/helpers.rb +71 -0
  50. data/lib/greenhat/thing/history.rb +51 -0
  51. data/lib/greenhat/thing/info_format.rb +193 -0
  52. data/lib/greenhat/thing/kind.rb +97 -0
  53. data/lib/greenhat/thing/spinner.rb +52 -0
  54. data/lib/greenhat/thing/super_log.rb +102 -0
  55. data/lib/greenhat/tty/custom_line.rb +29 -0
  56. data/lib/greenhat/tty/line.rb +326 -0
  57. data/lib/greenhat/tty/reader.rb +110 -0
  58. data/lib/greenhat/version.rb +3 -0
  59. data/lib/greenhat/views/css.slim +126 -0
  60. data/lib/greenhat/views/disk_free.slim +18 -0
  61. data/lib/greenhat/views/index.slim +51 -0
  62. data/lib/greenhat/views/info.slim +39 -0
  63. data/lib/greenhat/views/manifest.slim +22 -0
  64. data/lib/greenhat/views/memory.slim +18 -0
  65. data/lib/greenhat/views/netstat.slim +20 -0
  66. data/lib/greenhat/views/process.slim +21 -0
  67. data/lib/greenhat/views/systemctl.slim +40 -0
  68. data/lib/greenhat/views/ulimit.slim +15 -0
  69. data/lib/greenhat/web.rb +46 -0
  70. 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,12 @@
1
+ module GreenHat
2
+ # CLI Helper
3
+ module Shell
4
+ # Logs
5
+ module Network
6
+ # Easy Show All
7
+ def self.netstat
8
+ ShellHelper.file_output GreenHat::Network.netstat
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module GreenHat
2
+ # CLI Helper
3
+ module Shell
4
+ # Logs
5
+ module Process
6
+ # Easy Show All
7
+ def self.ps
8
+ ShellHelper.file_output GreenHat::Ps.ps
9
+ end
10
+ end
11
+ end
12
+ 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