greenhat 0.3.3 → 0.4.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 +1 -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 +19 -8
- data/lib/greenhat/cli.rb +24 -126
- data/lib/greenhat/entrypoint.rb +170 -0
- data/lib/greenhat/host.rb +25 -37
- data/lib/greenhat/settings.rb +6 -0
- 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 +115 -106
- data/lib/greenhat/shell.rb +10 -3
- data/lib/greenhat/thing/file_types.rb +136 -8
- 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/kind.rb +5 -0
- data/lib/greenhat/thing.rb +11 -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 +2 -0
- metadata +78 -5
data/lib/greenhat/shell/log.rb
CHANGED
@@ -2,6 +2,7 @@ module GreenHat
|
|
2
2
|
# CLI Helper
|
3
3
|
module Shell
|
4
4
|
# Logs
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
5
6
|
module Log
|
6
7
|
def self.auto_complete(list, word)
|
7
8
|
# Argument Parsing
|
@@ -47,6 +48,7 @@ module GreenHat
|
|
47
48
|
puts ' filter'.pastel(:green)
|
48
49
|
puts " Primary way for log searching within greenhat. See #{'filter_help'.pastel(:blue)}"
|
49
50
|
puts ' Time, round, slice/except, and/or, stats, uniq, sort'
|
51
|
+
puts " #{'filter_help'.pastel(:blue)} supports filtering Ex: #{'filter_help stats'.pastel(:blue)}"
|
50
52
|
puts
|
51
53
|
|
52
54
|
puts ' show'.pastel(:green)
|
@@ -57,13 +59,33 @@ module GreenHat
|
|
57
59
|
puts " General full text by file searching. See #{'search_help'.pastel(:blue)}"
|
58
60
|
puts
|
59
61
|
|
62
|
+
puts ' save'.pastel(:green)
|
63
|
+
puts ' Save the last query result into a new searchable object'
|
64
|
+
puts
|
65
|
+
|
66
|
+
puts ' write'.pastel(:green)
|
67
|
+
puts ' Write the last query result into a local file'
|
68
|
+
puts
|
69
|
+
|
70
|
+
puts ' visualize'.pastel(:green)
|
71
|
+
puts ' Load web services and formulate last query for the UI'
|
72
|
+
puts
|
73
|
+
|
60
74
|
puts ShellHelper::List.help
|
61
75
|
|
62
76
|
puts "See #{'examples'.pastel(:bright_blue)} for query examples"
|
63
77
|
end
|
64
78
|
|
65
|
-
def self.filter_help
|
66
|
-
|
79
|
+
def self.filter_help(args = {})
|
80
|
+
if args.empty?
|
81
|
+
ShellHelper::Filter.help
|
82
|
+
else
|
83
|
+
list = ShellHelper::Filter.help_index.select do |k, _v|
|
84
|
+
k.to_s.include? args.first
|
85
|
+
end
|
86
|
+
|
87
|
+
puts list.values.map { |x| x.join("\n") }.join("\n\n")
|
88
|
+
end
|
67
89
|
end
|
68
90
|
|
69
91
|
def self.ls(args = [])
|
@@ -80,6 +102,95 @@ module GreenHat
|
|
80
102
|
ShellHelper.show files.map(&:data).flatten
|
81
103
|
end
|
82
104
|
|
105
|
+
def self.save(raw = [])
|
106
|
+
if ShellHelper::Log.last.nil?
|
107
|
+
puts 'No previous query found'.pastel(:red)
|
108
|
+
puts 'Run a query first then save to store as a new log'
|
109
|
+
puts
|
110
|
+
puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
name = if raw.empty?
|
115
|
+
Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
|
116
|
+
else
|
117
|
+
raw.first
|
118
|
+
end
|
119
|
+
|
120
|
+
if name.blank?
|
121
|
+
puts 'Name required'.pastel(:red)
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
|
125
|
+
results = ShellHelper.filter_internal ShellHelper::Log.last
|
126
|
+
|
127
|
+
# Don't save empty results
|
128
|
+
if results.empty?
|
129
|
+
puts 'No results'.pastel(:red)
|
130
|
+
ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
|
131
|
+
return false
|
132
|
+
end
|
133
|
+
|
134
|
+
Thing.new.query_save(results, name)
|
135
|
+
puts "#{name.pastel(:green)} Saved!"
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.write(raw = [])
|
139
|
+
if ShellHelper::Log.last.nil?
|
140
|
+
puts 'No previous query found'.pastel(:red)
|
141
|
+
puts 'Run a query first then write'
|
142
|
+
puts
|
143
|
+
puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'save'.pastel(:green)}"
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
name = if raw.empty?
|
148
|
+
Cli.prompt.ask('Log/name to save the results to? '.pastel(:yellow))
|
149
|
+
else
|
150
|
+
raw.first
|
151
|
+
end
|
152
|
+
|
153
|
+
if name.blank?
|
154
|
+
puts 'Name required'.pastel(:red)
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
|
158
|
+
results = ShellHelper.filter_internal ShellHelper::Log.last
|
159
|
+
|
160
|
+
# Don't save empty results
|
161
|
+
if results.empty?
|
162
|
+
puts 'No results'.pastel(:red)
|
163
|
+
ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
|
164
|
+
return false
|
165
|
+
end
|
166
|
+
|
167
|
+
all = results.map { |row| Oj.dump(row) }
|
168
|
+
File.write(name, all.join("\n"))
|
169
|
+
puts "#{name.pastel(:green)} File Written!"
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.visualize
|
173
|
+
if ShellHelper::Log.last.nil?
|
174
|
+
puts 'No previous query found'.pastel(:red)
|
175
|
+
puts 'Run a query first then visualize to load it in the chart web page'
|
176
|
+
puts
|
177
|
+
puts "Try #{'nginx/gitlab_access.log --status!=200'.pastel(:green)} then #{'visualize'.pastel(:green)}"
|
178
|
+
return true
|
179
|
+
end
|
180
|
+
|
181
|
+
# Load Required Files
|
182
|
+
require 'greenhat/web'
|
183
|
+
|
184
|
+
unless GreenHat::Web.alive?
|
185
|
+
GreenHat::Web.start
|
186
|
+
sleep 0.2
|
187
|
+
end
|
188
|
+
|
189
|
+
url = "http://localhost:4567/chart/time?query=#{CGI.escape(ShellHelper::Log.last)}"
|
190
|
+
|
191
|
+
GreenHat::Platform.open url
|
192
|
+
end
|
193
|
+
|
83
194
|
# ========================================================================
|
84
195
|
# Filter (See Filter Help)
|
85
196
|
# ========================================================================
|
@@ -94,6 +205,8 @@ module GreenHat
|
|
94
205
|
return true
|
95
206
|
end
|
96
207
|
|
208
|
+
ShellHelper::Log.last = raw
|
209
|
+
|
97
210
|
# Argument Parsing
|
98
211
|
files, flags, args = Args.parse(raw)
|
99
212
|
|
@@ -104,7 +217,7 @@ module GreenHat
|
|
104
217
|
|
105
218
|
# Skip and Print Total if set
|
106
219
|
if flags[:total]
|
107
|
-
ShellHelper.total_count(results)
|
220
|
+
ShellHelper.total_count(results, flags)
|
108
221
|
return true
|
109
222
|
end
|
110
223
|
|
@@ -117,13 +230,13 @@ module GreenHat
|
|
117
230
|
# Check Search Results
|
118
231
|
if results.instance_of?(Hash) && results.values.flatten.empty?
|
119
232
|
puts 'No results'.pastel(:red)
|
233
|
+
ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
|
234
|
+
elsif flags[:pipe]
|
235
|
+
Pipe.show(results, flags[:pipe])
|
120
236
|
else
|
121
237
|
# This causes the key 'colorized' output to also be included
|
122
238
|
ShellHelper.show(results.to_a.compact.flatten, flags)
|
123
239
|
end
|
124
|
-
|
125
|
-
# log filter --path='cloud/gitlab-automation' --path='/pull' --all
|
126
|
-
# log filter --project=thingy --other_filter=asdf *
|
127
240
|
rescue StandardError => e
|
128
241
|
LogBot.fatal('Filter', message: e.message)
|
129
242
|
ap e.backtrace
|
@@ -152,9 +265,25 @@ module GreenHat
|
|
152
265
|
puts 'Count/% occurences for both user and remote ip fields'.pastel(:bright_green)
|
153
266
|
puts 'gitlab-rails/api_json.log --stats=meta.user,meta.remote_ip --exists=meta.user'
|
154
267
|
puts
|
155
|
-
end
|
156
268
|
|
269
|
+
puts 'Sidekiq jobs that took over 5 seconds excluding LdapSyncWorker jobs'.pastel(:bright_green)
|
270
|
+
puts 'sidekiq/current --duration_s>=5 --class!=LdapSyncWorker'
|
271
|
+
puts
|
272
|
+
|
273
|
+
puts 'Search access logs for runner requests, exclude specific runner version'.pastel(:bright_green)
|
274
|
+
puts 'nginx/gitlab_access.log --http_user_agent=gitlab-runner --http_user_agent!=13.12.0'
|
275
|
+
puts
|
276
|
+
|
277
|
+
puts 'Get a list of unique Gitaly error messages for a specific project'.pastel(:bright_green)
|
278
|
+
puts 'filter --level=error --grpc.request.glProjectPath=path/to/project gitaly/current --slice=error --uniq=error'
|
279
|
+
puts
|
280
|
+
|
281
|
+
puts 'Show workhorse duration/URI. Filter by duration bounds'.pastel(:bright_green)
|
282
|
+
puts 'gitlab-workhorse/current --duration_ms>=30000 --duration_ms<=45000 --slice=duration_ms,uri'
|
283
|
+
puts
|
284
|
+
end
|
157
285
|
# rubocop:enable Layout/LineLength
|
286
|
+
|
158
287
|
# ========================================================================
|
159
288
|
# Search (Full Text / String Search)
|
160
289
|
# ========================================================================
|
@@ -169,13 +298,15 @@ module GreenHat
|
|
169
298
|
|
170
299
|
# Skip and Print Total if set
|
171
300
|
if flags[:total]
|
172
|
-
ShellHelper.total_count(results)
|
301
|
+
ShellHelper.total_count(results, flags)
|
173
302
|
return true
|
174
303
|
end
|
175
304
|
|
176
305
|
# Check Search Results
|
177
306
|
if results.values.flatten.empty?
|
178
307
|
puts 'No results'.pastel(:red)
|
308
|
+
ShellHelper::Log.no_files_warning(files) if ShellHelper.find_things(files, flags).count.zero?
|
309
|
+
|
179
310
|
else
|
180
311
|
# This causes the key 'colorized' output to also be included
|
181
312
|
ShellHelper.show(results.to_a.compact.flatten, flags)
|
@@ -240,7 +371,7 @@ module GreenHat
|
|
240
371
|
puts 'log search --text=BuildHooksWorker --text!=start --slice=enqueued_at sidekiq/current'
|
241
372
|
puts
|
242
373
|
end
|
243
|
-
# rubocop:enable Metrics/MethodLength
|
374
|
+
# rubocop:enable Metrics/MethodLength,Metrics/ModuleLength
|
244
375
|
|
245
376
|
# ------------------------------------------------------------------------
|
246
377
|
end
|
@@ -251,9 +382,22 @@ module GreenHat
|
|
251
382
|
module ShellHelper
|
252
383
|
# Log Helpers
|
253
384
|
module Log
|
385
|
+
def self.last=(value)
|
386
|
+
@last = value.join(' ')
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.last
|
390
|
+
@last
|
391
|
+
end
|
392
|
+
|
254
393
|
def self.list
|
255
394
|
Thing.all.select(&:log)
|
256
395
|
end
|
396
|
+
|
397
|
+
def self.no_files_warning(files)
|
398
|
+
puts "No matching files found for pattern #{files.to_s.pastel(:yellow)}"
|
399
|
+
puts "See #{'ls'.pastel(:blue)} for available files"
|
400
|
+
end
|
257
401
|
end
|
258
402
|
# --------
|
259
403
|
end
|
@@ -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
|