greenhat 0.7.4 → 0.9.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/README.md +3 -1
- data/lib/greenhat/accessors/gitlab.rb +1 -1
- data/lib/greenhat/accessors/grep.rb +1 -1
- data/lib/greenhat/archive.rb +24 -9
- data/lib/greenhat/cli.rb +4 -4
- data/lib/greenhat/entrypoint.rb +1 -1
- data/lib/greenhat/host.rb +2 -2
- data/lib/greenhat/paper/paper_helper.rb +1 -1
- data/lib/greenhat/reports/internal_methods.rb +2 -2
- data/lib/greenhat/reports/shell_helper.rb +5 -5
- data/lib/greenhat/reports.rb +3 -3
- data/lib/greenhat/shell/args.rb +3 -3
- data/lib/greenhat/shell/faststats.rb +2 -2
- data/lib/greenhat/shell/query.rb +2 -4
- data/lib/greenhat/shell/reports.rb +1 -1
- data/lib/greenhat/shell/shell_helper.rb +1 -1
- data/lib/greenhat/shell.rb +1 -1
- data/lib/greenhat/thing/formatters/api_json.rb +1 -1
- data/lib/greenhat/thing/formatters/exporters.rb +1 -1
- data/lib/greenhat/thing/formatters/gitlab_status.rb +1 -1
- data/lib/greenhat/thing/formatters/identify_db.rb +2 -2
- data/lib/greenhat/thing/formatters/json.rb +1 -1
- data/lib/greenhat/thing/formatters/kube_nginx.rb +10 -10
- data/lib/greenhat/thing/formatters/nginx.rb +10 -10
- data/lib/greenhat/thing/formatters/runner_log.rb +9 -7
- data/lib/greenhat/thing/formatters/syslog.rb +4 -4
- data/lib/greenhat/thing/formatters/time_json.rb +1 -1
- data/lib/greenhat/thing/formatters/time_space.rb +1 -1
- data/lib/greenhat/thing/history.rb +1 -3
- data/lib/greenhat/thing/info_format.rb +3 -3
- data/lib/greenhat/thing/spinner.rb +1 -1
- data/lib/greenhat/thing.rb +1 -1
- data/lib/greenhat/version.rb +1 -1
- data/lib/greenhat/web/helpers.rb +4 -8
- metadata +19 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e82009def6948189e79cbb259088c57da5e1f22aa50e5c47a560a76e2a16e9e
|
4
|
+
data.tar.gz: 31fe1dbb0ec0d740e962942f6d4c511b8c5facebee16411e6daa0d93c1272d47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1719579efd5c3accf5fe402c5b57e51c7613a530bc715f0e71e88e6a8dac7ac05cb965e4d0351dcd8a0fe38aefe4f16dbf76c4692fe93cff40aa325c38a72a10
|
7
|
+
data.tar.gz: d953dec24128e963a38b58949214d76b5ae32ee0a0f01f47997201ff07a24219f2b8911e06121022d76235ccf5105766cbacb39f3402d796484ef0e24799d42e
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Experimental SOS and Log Parser for GitLab
|
|
12
12
|
|
13
13
|
`apt-get install libarchive-tools`
|
14
14
|
|
15
|
-
Ruby 3.
|
15
|
+
Ruby 3.1 or later, including the headers (such as package `ruby-dev`)
|
16
16
|
|
17
17
|
## Installation
|
18
18
|
|
@@ -29,6 +29,8 @@ greenhat production_json.log
|
|
29
29
|
>> help # the program is self-documented through the builtin help command
|
30
30
|
```
|
31
31
|
|
32
|
+
[Watch an introductory tutorial](https://www.youtube.com/watch?v=xyVQiKFgeP4) (recorded March 5, 2024, with Green Hat v0.7.3)
|
33
|
+
|
32
34
|
## Testing
|
33
35
|
|
34
36
|
```
|
data/lib/greenhat/archive.rb
CHANGED
@@ -26,7 +26,7 @@ module GreenHat
|
|
26
26
|
# Ignore Empty Files
|
27
27
|
list.reject! { |x| File.empty?(x) }
|
28
28
|
|
29
|
-
archive = Archive.new(name: archive_path, path:)
|
29
|
+
archive = Archive.new(name: archive_path, path: path)
|
30
30
|
archive.save
|
31
31
|
|
32
32
|
list.each do |file|
|
@@ -34,7 +34,7 @@ module GreenHat
|
|
34
34
|
next if missing_command(file)
|
35
35
|
|
36
36
|
# Thread.new do
|
37
|
-
thing = archive.things_create(file:)
|
37
|
+
thing = archive.things_create(file: file)
|
38
38
|
thing.setup
|
39
39
|
# end
|
40
40
|
end
|
@@ -50,23 +50,29 @@ module GreenHat
|
|
50
50
|
|
51
51
|
# Handle Different Types of Archives
|
52
52
|
def self.unpack(archive_path, path)
|
53
|
+
decompressed_successfully = false
|
54
|
+
|
53
55
|
case File.extname archive_path
|
54
|
-
when '.tgz'
|
55
|
-
base_path = archive_path.gsub(File.basename(archive_path), '')
|
56
|
-
`bsdtar -xzf "#{archive_path}" -C #{base_path}`
|
57
|
-
FileUtils.rm(archive_path)
|
58
|
-
when '.tar'
|
56
|
+
when '.tgz', '.tar'
|
59
57
|
base_path = archive_path.gsub(File.basename(archive_path), '')
|
60
58
|
`bsdtar -xf "#{archive_path}" -C #{base_path}`
|
61
|
-
|
59
|
+
|
60
|
+
decompressed_successfully = $CHILD_STATUS.success?
|
61
|
+
FileUtils.rm(archive_path) if decompressed_successfully
|
62
62
|
when '.gz'
|
63
63
|
`gzip -d "#{archive_path}"`
|
64
|
+
|
65
|
+
decompressed_successfully = $CHILD_STATUS.success?
|
64
66
|
when '.zip'
|
65
67
|
base_path = archive_path.gsub(File.basename(archive_path), '')
|
66
68
|
`unzip -o -d "#{base_path}" #{archive_path}`
|
67
|
-
|
69
|
+
|
70
|
+
decompressed_successfully = $CHILD_STATUS.success?
|
71
|
+
FileUtils.rm(archive_path) if decompressed_successfully
|
68
72
|
when '.bz2'
|
69
73
|
`bzip2 -d #{archive_path}`
|
74
|
+
|
75
|
+
decompressed_successfully = $CHILD_STATUS.success?
|
70
76
|
when '.s'
|
71
77
|
# Find Original Directory, Split Path, Rename to .gz
|
72
78
|
base_path = archive_path.gsub(File.basename(archive_path), '')
|
@@ -74,6 +80,15 @@ module GreenHat
|
|
74
80
|
else
|
75
81
|
FileUtils.cp(archive_path, "#{path}/#{archive_path}")
|
76
82
|
end
|
83
|
+
|
84
|
+
# if the archive is corrupt and doesn't uncompress properly, greenhat will keep looping on it endlessly,
|
85
|
+
# trying to decompress it. let's rename it on failure so it stops trying, but the SE can still try and
|
86
|
+
# extract it themselves.
|
87
|
+
return if decompressed_successfully
|
88
|
+
|
89
|
+
puts "#{'Decompress failed'.pastel(:red)}: #{archive_path.pastel(:green)}"
|
90
|
+
puts 'Renaming file so we can continue.'.pastel(:yellow)
|
91
|
+
FileUtils.mv(archive_path, "#{archive_path}.broken")
|
77
92
|
end
|
78
93
|
|
79
94
|
def self.archive?(file_name)
|
data/lib/greenhat/cli.rb
CHANGED
@@ -8,7 +8,7 @@ module GreenHat
|
|
8
8
|
value ||= '' # Empty Start
|
9
9
|
|
10
10
|
loop do
|
11
|
-
line = reader.read_line(readline_notch, value:)
|
11
|
+
line = reader.read_line(readline_notch, value: value)
|
12
12
|
value = '' # Remove Afterwards
|
13
13
|
|
14
14
|
if reader.breaker
|
@@ -169,7 +169,7 @@ module GreenHat
|
|
169
169
|
def self.did_you_mean
|
170
170
|
dictionary = current_methods + current_submodules + cmd_list
|
171
171
|
|
172
|
-
all = DidYouMean::SpellChecker.new(dictionary:).correct(cmd)
|
172
|
+
all = DidYouMean::SpellChecker.new(dictionary: dictionary).correct(cmd)
|
173
173
|
|
174
174
|
if all.empty?
|
175
175
|
puts [
|
@@ -403,8 +403,8 @@ module GreenHat
|
|
403
403
|
def self.suppress_output
|
404
404
|
original_stderr = $stderr.clone
|
405
405
|
original_stdout = $stdout.clone
|
406
|
-
$stderr.reopen(File.new(
|
407
|
-
$stdout.reopen(File.new(
|
406
|
+
$stderr.reopen(File.new(File::NULL, 'w'))
|
407
|
+
$stdout.reopen(File.new(File::NULL, 'w'))
|
408
408
|
yield
|
409
409
|
ensure
|
410
410
|
$stdout.reopen(original_stdout)
|
data/lib/greenhat/entrypoint.rb
CHANGED
@@ -12,7 +12,7 @@ module GreenHat
|
|
12
12
|
load_files files
|
13
13
|
Cli.run_command(args) if args.any? { |arg| Cli.run_command?(arg) }
|
14
14
|
|
15
|
-
report_runner(args
|
15
|
+
report_runner(args: args, files: files, raw: raw, flags: flags)
|
16
16
|
post_args(flags)
|
17
17
|
post_setup(flags)
|
18
18
|
|
data/lib/greenhat/host.rb
CHANGED
@@ -152,7 +152,7 @@ class Host < Teron
|
|
152
152
|
obj[headers[i]] = v
|
153
153
|
end
|
154
154
|
end
|
155
|
-
{ headers
|
155
|
+
{ headers: headers, list: list }
|
156
156
|
end
|
157
157
|
|
158
158
|
def systemctl_unit_files
|
@@ -161,7 +161,7 @@ class Host < Teron
|
|
161
161
|
|
162
162
|
all = file.raw[1..-2].map do |x|
|
163
163
|
unit, status = x.split
|
164
|
-
{ unit
|
164
|
+
{ unit: unit, status: status }
|
165
165
|
end
|
166
166
|
|
167
167
|
all.reject! { |x| x[:unit].nil? }
|
@@ -105,7 +105,7 @@ module GreenHat
|
|
105
105
|
# --------------------------------------------------------------------------
|
106
106
|
def render_array_table(entry)
|
107
107
|
header, rows = entry
|
108
|
-
table = TTY::Table.new(header
|
108
|
+
table = TTY::Table.new(header: header, rows: rows)
|
109
109
|
|
110
110
|
LogBot.debug('Rendering Entries') if ENV['DEBUG']
|
111
111
|
self.output = table.render(table_style, padding: [0, 1, 0, 1], multiline: true) do |renderer|
|
@@ -125,10 +125,10 @@ module GreenHat
|
|
125
125
|
def show(value)
|
126
126
|
if value.instance_of?(Array)
|
127
127
|
value.each do |x|
|
128
|
-
output.push GreenHat::Paper.new(data: x, flags:).render
|
128
|
+
output.push GreenHat::Paper.new(data: x, flags: flags).render
|
129
129
|
end
|
130
130
|
else
|
131
|
-
output.push GreenHat::Paper.new(data: value, flags:).render
|
131
|
+
output.push GreenHat::Paper.new(data: value, flags: flags).render
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
@@ -4,7 +4,7 @@ module GreenHat
|
|
4
4
|
module Reports
|
5
5
|
# Make Running more consistent
|
6
6
|
def self.run(file:, args:, flags:, raw_args:)
|
7
|
-
report = GreenHat::Reports::Builder.new(file
|
7
|
+
report = GreenHat::Reports::Builder.new(file: file, args: args, flags: flags, raw_args: raw_args)
|
8
8
|
|
9
9
|
# Fuzzy Match Archive Names
|
10
10
|
archives = Archive.all
|
@@ -18,11 +18,11 @@ module GreenHat
|
|
18
18
|
|
19
19
|
output = archives.map do |archive|
|
20
20
|
runner = GreenHat::Reports::Runner.new(
|
21
|
-
archive
|
21
|
+
archive: archive,
|
22
22
|
store: report.store.clone,
|
23
|
-
flags
|
24
|
-
args
|
25
|
-
raw_args:
|
23
|
+
flags: flags,
|
24
|
+
args: args,
|
25
|
+
raw_args: raw_args
|
26
26
|
)
|
27
27
|
|
28
28
|
runner.run!
|
data/lib/greenhat/reports.rb
CHANGED
data/lib/greenhat/shell/args.rb
CHANGED
@@ -3,7 +3,7 @@ module GreenHat
|
|
3
3
|
module Shell
|
4
4
|
# Logs
|
5
5
|
module Faststats
|
6
|
-
# rubocop:disable Metrics/MethodLength
|
6
|
+
# rubocop:disable Metrics/MethodLength
|
7
7
|
def self.help
|
8
8
|
puts "\u2500".pastel(:cyan) * 25
|
9
9
|
puts "Gimme #{'Performance Stats'.pastel(:yellow)}"
|
@@ -65,7 +65,7 @@ module GreenHat
|
|
65
65
|
puts ' errors gitaly/current'
|
66
66
|
puts ' errors gitaly/current --no-border'
|
67
67
|
end
|
68
|
-
# rubocop:enable Metrics/MethodLength
|
68
|
+
# rubocop:enable Metrics/MethodLength
|
69
69
|
|
70
70
|
def self.faststats_installation
|
71
71
|
puts "#{'Unable to find'.pastel(:red)} #{'fast-stats'.pastel(:blue)}"
|
data/lib/greenhat/shell/query.rb
CHANGED
@@ -242,8 +242,6 @@ module GreenHat
|
|
242
242
|
index
|
243
243
|
rescue StandardError => e
|
244
244
|
LogBot.fatal('Index Error', e.message)
|
245
|
-
ensure
|
246
|
-
{}
|
247
245
|
end
|
248
246
|
|
249
247
|
# Limit / Ensure Exists and Valid Number
|
@@ -378,7 +376,7 @@ module GreenHat
|
|
378
376
|
l95 = l95.round(flags.round) if flags.round
|
379
377
|
|
380
378
|
{
|
381
|
-
key
|
379
|
+
key: key,
|
382
380
|
'99' => l99,
|
383
381
|
'95' => l95,
|
384
382
|
mean: flags.round ? values.mean.round(flags.round) : values.mean,
|
@@ -500,7 +498,7 @@ module GreenHat
|
|
500
498
|
match
|
501
499
|
end
|
502
500
|
rescue StandardError => e
|
503
|
-
LogBot.fatal('[Query] Filter Row Failure', message: e.message, backtrace: e.backtrace.first, row:)
|
501
|
+
LogBot.fatal('[Query] Filter Row Failure', message: e.message, backtrace: e.backtrace.first, row: row)
|
504
502
|
false
|
505
503
|
end
|
506
504
|
|
@@ -57,7 +57,7 @@ module GreenHat
|
|
57
57
|
|
58
58
|
run_list.each do |file|
|
59
59
|
raw_args = list.reject { |x| file.include? x }
|
60
|
-
GreenHat::ShellHelper::Reports.run(file
|
60
|
+
GreenHat::ShellHelper::Reports.run(file: file, args: args, flags: flags, raw_args: raw_args)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
data/lib/greenhat/shell.rb
CHANGED
@@ -34,7 +34,7 @@ module GreenHat
|
|
34
34
|
|
35
35
|
def flatten_hash(param, prefix = nil)
|
36
36
|
param.each_pair.reduce({}) do |a, (k, v)|
|
37
|
-
v.is_a?(Hash) ? a.merge(flatten_hash(v, "#{prefix}#{k}.")) : a.merge("#{prefix}#{k}"
|
37
|
+
v.is_a?(Hash) ? a.merge(flatten_hash(v, "#{prefix}#{k}.")) : a.merge("#{prefix}#{k}": v)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -31,7 +31,7 @@ module GreenHat
|
|
31
31
|
# Breakout
|
32
32
|
method, path, protocol, status, bytes = request.gsub('"', '').split(' ', 5)
|
33
33
|
|
34
|
-
result.merge!(method
|
34
|
+
result.merge!(method: method, path: path, protocol: protocol, status: status, bytes: bytes)
|
35
35
|
|
36
36
|
else
|
37
37
|
result[:msg] = msg
|
@@ -18,16 +18,16 @@ module GreenHat
|
|
18
18
|
|
19
19
|
{
|
20
20
|
remote_addr: ip,
|
21
|
-
remote_user
|
22
|
-
method
|
23
|
-
path
|
24
|
-
status
|
25
|
-
bytes
|
26
|
-
http_version
|
27
|
-
http_referer
|
28
|
-
http_user_agent
|
29
|
-
gzip_ratio
|
30
|
-
time:
|
21
|
+
remote_user: remote_user,
|
22
|
+
method: method,
|
23
|
+
path: path,
|
24
|
+
status: status,
|
25
|
+
bytes: bytes,
|
26
|
+
http_version: http_version,
|
27
|
+
http_referer: http_referer,
|
28
|
+
http_user_agent: http_user_agent,
|
29
|
+
gzip_ratio: gzip_ratio,
|
30
|
+
time: time
|
31
31
|
}
|
32
32
|
|
33
33
|
# Fall back for malformed logs
|
@@ -21,16 +21,16 @@ module GreenHat
|
|
21
21
|
|
22
22
|
{
|
23
23
|
remote_addr: ip,
|
24
|
-
remote_user
|
25
|
-
method
|
26
|
-
path
|
27
|
-
status
|
28
|
-
bytes
|
29
|
-
http_version
|
30
|
-
http_referer
|
31
|
-
http_user_agent
|
32
|
-
gzip_ratio
|
33
|
-
time:
|
24
|
+
remote_user: remote_user,
|
25
|
+
method: method,
|
26
|
+
path: path,
|
27
|
+
status: status,
|
28
|
+
bytes: bytes,
|
29
|
+
http_version: http_version,
|
30
|
+
http_referer: http_referer,
|
31
|
+
http_user_agent: http_user_agent,
|
32
|
+
gzip_ratio: gzip_ratio,
|
33
|
+
time: time
|
34
34
|
}
|
35
35
|
rescue StandardError => e
|
36
36
|
LogBot.warn('NGINX Parse', e.message)
|
@@ -19,15 +19,17 @@ module GreenHat
|
|
19
19
|
def process_runner_log_row(row)
|
20
20
|
month, day, time, device, service, message = row.split(' ', 6)
|
21
21
|
service&.gsub!(':', '')
|
22
|
+
# rubocop:disable Style/SafeNavigationChainLength
|
22
23
|
pid = service&.match(/\[(.*?)\]/)&.captures&.first
|
24
|
+
# rubocop:enable Style/SafeNavigationChainLength
|
23
25
|
|
24
26
|
entry = {
|
25
27
|
time: format_time_parse("#{month} #{day} #{time}"),
|
26
|
-
device
|
27
|
-
service
|
28
|
-
message
|
29
|
-
pid
|
30
|
-
row: # Insert full row for different timestamp formats
|
28
|
+
device: device,
|
29
|
+
service: service,
|
30
|
+
message: message,
|
31
|
+
pid: pid,
|
32
|
+
row: row # Insert full row for different timestamp formats
|
31
33
|
}
|
32
34
|
|
33
35
|
# Remove Empty Keys
|
@@ -54,9 +56,9 @@ module GreenHat
|
|
54
56
|
# Don't overwrite stuff
|
55
57
|
case key
|
56
58
|
when 'time'
|
57
|
-
entry["#{key}-msg"
|
59
|
+
entry[:"#{key}-msg"] = format_time_parse value
|
58
60
|
when 'message', :message
|
59
|
-
entry["#{key}-msg"
|
61
|
+
entry[:"#{key}-msg"] = value
|
60
62
|
else
|
61
63
|
entry[key.to_sym] = value
|
62
64
|
end
|
@@ -16,8 +16,6 @@ module GreenHat
|
|
16
16
|
else
|
17
17
|
{}
|
18
18
|
end
|
19
|
-
ensure
|
20
|
-
{}
|
21
19
|
end
|
22
20
|
|
23
21
|
def self.file
|
@@ -34,7 +32,7 @@ module GreenHat
|
|
34
32
|
end
|
35
33
|
|
36
34
|
def self.add(name, type)
|
37
|
-
files[name] = { type
|
35
|
+
files[name] = { type: type, time: expire }
|
38
36
|
|
39
37
|
write
|
40
38
|
end
|
@@ -3,7 +3,7 @@ module GreenHat
|
|
3
3
|
module InfoFormat
|
4
4
|
# Is this something that can be formatted by info?
|
5
5
|
def info?
|
6
|
-
methods.include? "format_#{path}"
|
6
|
+
methods.include? :"format_#{path}"
|
7
7
|
end
|
8
8
|
|
9
9
|
# Handle Info Formatting
|
@@ -103,7 +103,7 @@ module GreenHat
|
|
103
103
|
obj[headers[i]] = v
|
104
104
|
end
|
105
105
|
end
|
106
|
-
{ headers
|
106
|
+
{ headers: headers, list: list }
|
107
107
|
end
|
108
108
|
|
109
109
|
def manifest_json_format
|
@@ -150,7 +150,7 @@ module GreenHat
|
|
150
150
|
|
151
151
|
all = info.systemctl_unit_files[1..-2].map do |x|
|
152
152
|
unit, status = x.split
|
153
|
-
{ unit
|
153
|
+
{ unit: unit, status: status }
|
154
154
|
end
|
155
155
|
all.reject! { |x| x[:unit].nil? }
|
156
156
|
all.sort_by(&:unit)
|
@@ -9,7 +9,7 @@ module GreenHat
|
|
9
9
|
@spinner = TTY::Spinner.new(
|
10
10
|
"#{time.pastel(:bright_black)} - [:spinner] :title", hide_cursor: true, success_mark: '✔'.pastel(:green)
|
11
11
|
)
|
12
|
-
@spinner.update(title:)
|
12
|
+
@spinner.update(title: title)
|
13
13
|
|
14
14
|
# Don't Auto spin when debug output is happening
|
15
15
|
@spinner.auto_spin unless ENV['DEBUG']
|
data/lib/greenhat/thing.rb
CHANGED
data/lib/greenhat/version.rb
CHANGED
data/lib/greenhat/web/helpers.rb
CHANGED
@@ -22,8 +22,6 @@ module WebHelpers
|
|
22
22
|
index
|
23
23
|
rescue StandardError => e
|
24
24
|
LogBot.fatal('Index Error', e.message)
|
25
|
-
ensure
|
26
|
-
{}
|
27
25
|
end
|
28
26
|
|
29
27
|
# Without Grouping Just total count
|
@@ -94,7 +92,7 @@ module WebHelpers
|
|
94
92
|
end
|
95
93
|
|
96
94
|
# Transform / Calculate
|
97
|
-
list.
|
95
|
+
list.each_value do |v|
|
98
96
|
v.data.transform_values! do |l|
|
99
97
|
l.empty? ? 0 : l.sum
|
100
98
|
end
|
@@ -122,7 +120,7 @@ module WebHelpers
|
|
122
120
|
end
|
123
121
|
|
124
122
|
# Transform / Calculate
|
125
|
-
list.
|
123
|
+
list.each_value do |v|
|
126
124
|
v.data.transform_values! do |l|
|
127
125
|
l.empty? ? 0 : (l.sum / l.size.to_f) # .round(1)
|
128
126
|
end
|
@@ -149,9 +147,7 @@ module WebHelpers
|
|
149
147
|
stacks.each do |stack|
|
150
148
|
data = default_index.clone
|
151
149
|
data.transform_values! { |_y| [] } # Ensure Uniq
|
152
|
-
list[stack] = {
|
153
|
-
name: stack, data:
|
154
|
-
}
|
150
|
+
list[stack] = { name: stack, data: data }
|
155
151
|
end
|
156
152
|
|
157
153
|
# Collect Stack Data
|
@@ -164,7 +160,7 @@ module WebHelpers
|
|
164
160
|
end
|
165
161
|
|
166
162
|
# Transform / Calculate
|
167
|
-
list.
|
163
|
+
list.each_value do |v|
|
168
164
|
v.data.transform_values! do |l|
|
169
165
|
l.empty? ? 0 : (l.sum / l.size.to_f)
|
170
166
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: greenhat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Davin Walker
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-03 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: actionview
|
@@ -66,20 +65,34 @@ dependencies:
|
|
66
65
|
- - "~>"
|
67
66
|
- !ruby/object:Gem::Version
|
68
67
|
version: '4.1'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: concurrent-ruby
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.3.4
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.3.4
|
69
82
|
- !ruby/object:Gem::Dependency
|
70
83
|
name: did_you_mean
|
71
84
|
requirement: !ruby/object:Gem::Requirement
|
72
85
|
requirements:
|
73
86
|
- - "~>"
|
74
87
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
88
|
+
version: '2.0'
|
76
89
|
type: :runtime
|
77
90
|
prerelease: false
|
78
91
|
version_requirements: !ruby/object:Gem::Requirement
|
79
92
|
requirements:
|
80
93
|
- - "~>"
|
81
94
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
95
|
+
version: '2.0'
|
83
96
|
- !ruby/object:Gem::Dependency
|
84
97
|
name: dotenv
|
85
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -531,7 +544,6 @@ licenses:
|
|
531
544
|
- MIT
|
532
545
|
metadata:
|
533
546
|
rubygems_mfa_required: 'true'
|
534
|
-
post_install_message:
|
535
547
|
rdoc_options: []
|
536
548
|
require_paths:
|
537
549
|
- lib
|
@@ -546,8 +558,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
546
558
|
- !ruby/object:Gem::Version
|
547
559
|
version: '0'
|
548
560
|
requirements: []
|
549
|
-
rubygems_version: 3.
|
550
|
-
signing_key:
|
561
|
+
rubygems_version: 3.6.2
|
551
562
|
specification_version: 4
|
552
563
|
summary: GitLab SOS Tool
|
553
564
|
test_files: []
|