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
@@ -0,0 +1,65 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
# ======================================================
|
4
|
+
# == [ Gitaly ]
|
5
|
+
# ======================================================
|
6
|
+
def gitaly_avg_method_series
|
7
|
+
query = [
|
8
|
+
'gitaly/current',
|
9
|
+
'--slice=time,grpc.time_ms,grpc.method',
|
10
|
+
'--exists=grpc.time_ms',
|
11
|
+
'--exists=time',
|
12
|
+
'--grpc.time_ms>=1'
|
13
|
+
].join(' ')
|
14
|
+
|
15
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
16
|
+
|
17
|
+
method_key = :'grpc.method'
|
18
|
+
duration_key = :'grpc.time_ms'
|
19
|
+
|
20
|
+
# Collect Total by Duration
|
21
|
+
top = results.each_with_object({}) do |v, obj|
|
22
|
+
obj[v[method_key]] ||= []
|
23
|
+
obj[v[method_key]] << v[duration_key]
|
24
|
+
|
25
|
+
obj
|
26
|
+
end
|
27
|
+
|
28
|
+
# Average / Round
|
29
|
+
top.transform_values! { |x| (x.sum / x.size.to_f).round(1) }
|
30
|
+
|
31
|
+
# Only top by duration
|
32
|
+
method_calls = top.sort_by(&:last)[-10..].map(&:first)
|
33
|
+
results.select! { |x| method_calls.any? x[method_key] }
|
34
|
+
|
35
|
+
build_group_time_avg(results, method_key, duration_key) # 5 Minutes
|
36
|
+
end
|
37
|
+
|
38
|
+
def gitaly_errors_series
|
39
|
+
query = [
|
40
|
+
'gitaly/current',
|
41
|
+
'--slice=time,grpc.method,level',
|
42
|
+
'--exists=time,level,time',
|
43
|
+
'--level=error'
|
44
|
+
].join(' ')
|
45
|
+
|
46
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
47
|
+
|
48
|
+
build_group_time_total(results, :'grpc.method')
|
49
|
+
end
|
50
|
+
|
51
|
+
def gitaly_duration
|
52
|
+
query = [
|
53
|
+
'gitaly/current',
|
54
|
+
'--slice=time,grpc.time_ms',
|
55
|
+
'--exists=grpc.time_ms',
|
56
|
+
'--exists=time',
|
57
|
+
'--grpc.time_ms!=0',
|
58
|
+
'--exact'
|
59
|
+
].join(' ')
|
60
|
+
|
61
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
62
|
+
|
63
|
+
build_percentile_list(results, :'grpc.time_ms')
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
def arg_parse
|
4
|
+
@files, @flags, @args = GreenHat::Args.parse Shellwords.split(params[:query])
|
5
|
+
end
|
6
|
+
|
7
|
+
def percent(value, total)
|
8
|
+
((value.to_i / total.to_f) * 100).round(2)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_time_index(results, interval = 5.minutes)
|
12
|
+
index = {}
|
13
|
+
start = results.min_by(&:time).time.floor(interval)
|
14
|
+
finish = results.max_by(&:time).time.floor(interval)
|
15
|
+
|
16
|
+
loop do
|
17
|
+
index[start] = 0
|
18
|
+
start += interval
|
19
|
+
break if start > finish
|
20
|
+
end
|
21
|
+
|
22
|
+
index
|
23
|
+
rescue StandardError => e
|
24
|
+
LogBot.fatal('Index Error', e.message)
|
25
|
+
ensure
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Without Grouping Just total count
|
30
|
+
def build_time_total(results, interval = 5.minutes)
|
31
|
+
index = build_time_index(results, interval)
|
32
|
+
|
33
|
+
results.each do |r|
|
34
|
+
index[r.time.floor(interval)] += 1
|
35
|
+
end
|
36
|
+
|
37
|
+
index
|
38
|
+
end
|
39
|
+
|
40
|
+
# Without Grouping Avg
|
41
|
+
def build_time_avg(results, sum, interval = 5.minutes)
|
42
|
+
index = build_time_index(results, interval)
|
43
|
+
|
44
|
+
index.transform_values! { |_y| [] }
|
45
|
+
|
46
|
+
results.each do |r|
|
47
|
+
index[r.time.floor(interval)] << r[sum]
|
48
|
+
end
|
49
|
+
|
50
|
+
index.transform_values! do |l|
|
51
|
+
(l.sum / l.size.to_f)
|
52
|
+
end
|
53
|
+
|
54
|
+
index
|
55
|
+
end
|
56
|
+
|
57
|
+
# by Occurance/Count / Key to String
|
58
|
+
def build_group_time_total(results, group, interval = 5.minutes)
|
59
|
+
default_index = build_time_index(results, interval)
|
60
|
+
list = {}
|
61
|
+
|
62
|
+
results.map { |x| x[group] }.uniq.each do |key|
|
63
|
+
key = 'None' if key.nil?
|
64
|
+
list[key.to_s] = {
|
65
|
+
name: key.to_s,
|
66
|
+
data: default_index.clone
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
results.each do |r|
|
71
|
+
key = r[group].nil? ? 'None' : r[group]
|
72
|
+
list[key.to_s].data[r.time.floor(interval)] += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
list.values
|
76
|
+
end
|
77
|
+
|
78
|
+
# by Sum
|
79
|
+
def build_group_time_sum(results, group, sum, interval = 5.minutes)
|
80
|
+
default_index = build_time_index(results, interval)
|
81
|
+
list = {}
|
82
|
+
|
83
|
+
results.map { |x| x[group] }.uniq.each do |key|
|
84
|
+
key = 'None' if key.nil?
|
85
|
+
list[key] = {
|
86
|
+
name: key,
|
87
|
+
data: default_index.clone.transform_values { |_y| [] }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
results.each do |r|
|
92
|
+
key = r[group].nil? ? 'None' : r[group]
|
93
|
+
list[key].data[r.time.floor(interval)] << r[sum]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Transform / Calculate
|
97
|
+
list.each do |_k, v|
|
98
|
+
v.data.transform_values! do |l|
|
99
|
+
l.empty? ? 0 : l.sum
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
list.values
|
104
|
+
end
|
105
|
+
|
106
|
+
# by Avg
|
107
|
+
def build_group_time_avg(results, group, sum, interval = 5.minutes)
|
108
|
+
default_index = build_time_index(results, interval)
|
109
|
+
list = {}
|
110
|
+
|
111
|
+
results.map { |x| x[group] }.uniq.each do |key|
|
112
|
+
key = 'None' if key.nil?
|
113
|
+
list[key] = {
|
114
|
+
name: key,
|
115
|
+
data: default_index.clone.transform_values { |_y| [] }
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
results.each do |r|
|
120
|
+
key = r[group].nil? ? 'None' : r[group]
|
121
|
+
list[key].data[r.time.floor(interval)] << r[sum]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Transform / Calculate
|
125
|
+
list.each do |_k, v|
|
126
|
+
v.data.transform_values! do |l|
|
127
|
+
l.empty? ? 0 : (l.sum / l.size.to_f) # .round(1)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
list.values
|
132
|
+
end
|
133
|
+
|
134
|
+
# Stack AVG Helper
|
135
|
+
def build_stack_time_avg(results, stacks, interval = 5.minutes)
|
136
|
+
default_index = build_time_index(results, interval)
|
137
|
+
list = {}
|
138
|
+
|
139
|
+
# MS are Hard
|
140
|
+
# results.each do |r|
|
141
|
+
# stacks.each do |stack|
|
142
|
+
# next unless r[stack]
|
143
|
+
|
144
|
+
# r[stack] = r[stack] * 1000
|
145
|
+
# end
|
146
|
+
# end
|
147
|
+
|
148
|
+
# Build List
|
149
|
+
stacks.each do |stack|
|
150
|
+
data = default_index.clone
|
151
|
+
data.transform_values! { |_y| [] } # Ensure Uniq
|
152
|
+
list[stack] = {
|
153
|
+
name: stack, data: data
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Collect Stack Data
|
158
|
+
results.each do |r|
|
159
|
+
stacks.each do |stack|
|
160
|
+
next unless r.key?(stack)
|
161
|
+
|
162
|
+
list[stack].data[r.time.floor(interval)] << r[stack]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Transform / Calculate
|
167
|
+
list.each do |_k, v|
|
168
|
+
v.data.transform_values! do |l|
|
169
|
+
l.empty? ? 0 : (l.sum / l.size.to_f)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
list.values
|
174
|
+
end
|
175
|
+
|
176
|
+
# Helper to get the bottom Ten
|
177
|
+
def top_method_calls(list)
|
178
|
+
sorted = list.sort_by(&:last)
|
179
|
+
|
180
|
+
all = if sorted.length < 10
|
181
|
+
sorted
|
182
|
+
else
|
183
|
+
sorted[-10..]
|
184
|
+
end
|
185
|
+
|
186
|
+
all.map(&:first)
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_count_by_occurance(results, key, limit = 10)
|
190
|
+
output = results.each_with_object(Hash.new(0)) do |result, counts|
|
191
|
+
counts[result[key]] += 1
|
192
|
+
end
|
193
|
+
|
194
|
+
return output if limit.zero?
|
195
|
+
|
196
|
+
output.sort_by(&:last).reverse[0..(limit - 1)].to_h
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
# ======================================================
|
4
|
+
# == [ Production Log ]
|
5
|
+
# gitlab-rails/production_json.log
|
6
|
+
# ======================================================
|
7
|
+
# def prod_avg_path_duration_series
|
8
|
+
# query = [
|
9
|
+
# 'gitlab-rails/production_json.log',
|
10
|
+
# '--slice=time,path,duration_s'
|
11
|
+
# ].join(' ')
|
12
|
+
|
13
|
+
# results = GreenHat::ShellHelper.filter_internal(query)
|
14
|
+
|
15
|
+
# method_key = :path
|
16
|
+
# duration_key = :duration_s
|
17
|
+
|
18
|
+
# # Collect Total by Duration
|
19
|
+
# top = results.each_with_object({}) do |v, obj|
|
20
|
+
# obj[v[method_key]] ||= []
|
21
|
+
# obj[v[method_key]] << v[duration_key]
|
22
|
+
|
23
|
+
# obj
|
24
|
+
# end
|
25
|
+
|
26
|
+
# # Average / Round
|
27
|
+
# top.transform_values! { |x| (x.sum / x.size.to_f).round(1) }
|
28
|
+
|
29
|
+
# # Only top by duration
|
30
|
+
# method_calls = top_method_calls(top)
|
31
|
+
# results.select! { |x| method_calls.any? x[method_key] }
|
32
|
+
|
33
|
+
# build_group_time_avg(results, method_key, duration_key) # 5 Minutes
|
34
|
+
# end
|
35
|
+
|
36
|
+
def prod_status_series
|
37
|
+
query = [
|
38
|
+
'gitlab-rails/production_json.log',
|
39
|
+
'--slice=time,status',
|
40
|
+
'--exists=status'
|
41
|
+
].join(' ')
|
42
|
+
|
43
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
44
|
+
|
45
|
+
build_group_time_total(results, :status)
|
46
|
+
end
|
47
|
+
|
48
|
+
def prod_duration_series
|
49
|
+
query = [
|
50
|
+
'gitlab-rails/production_json.log',
|
51
|
+
'--slice=time,path,duration_s',
|
52
|
+
'--exists=duration_s'
|
53
|
+
].join(' ')
|
54
|
+
|
55
|
+
# MS is hard
|
56
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
57
|
+
results.each do |r|
|
58
|
+
r[:duration_s] = r[:duration_s] * 1000
|
59
|
+
end
|
60
|
+
|
61
|
+
# build_time_avg(results, :duration_s).transform_values { |x| x.round(1) }
|
62
|
+
build_percentile_list(results, :duration_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
def prod_duration_series_stacked
|
66
|
+
stacks = %i[
|
67
|
+
redis_duration_s
|
68
|
+
view_duration_s
|
69
|
+
gitaly_duration_s
|
70
|
+
redis_cache_duration_s
|
71
|
+
duration_s
|
72
|
+
db_duration_s
|
73
|
+
redis_shared_state_duration_s
|
74
|
+
queue_duration_s
|
75
|
+
db_duration_s
|
76
|
+
]
|
77
|
+
|
78
|
+
query = [
|
79
|
+
'gitlab-rails/production_json.log',
|
80
|
+
"--slice=time,#{stacks.join(',')}"
|
81
|
+
].join(' ')
|
82
|
+
|
83
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
84
|
+
|
85
|
+
output = build_stack_time_avg(results, stacks)
|
86
|
+
|
87
|
+
# MS are hard
|
88
|
+
output.each do |dataset|
|
89
|
+
dataset[:data].transform_values! { |x| (x * 1000).round(1) }
|
90
|
+
end
|
91
|
+
|
92
|
+
output
|
93
|
+
end
|
94
|
+
|
95
|
+
def prod_duration_pie
|
96
|
+
all = faststats_run('gitlab-rails/production_json.log', 'top')[:totals]
|
97
|
+
|
98
|
+
# Grab Specifics
|
99
|
+
totals = all.except(:count, :fails).transform_values(&:round)
|
100
|
+
|
101
|
+
# Organize
|
102
|
+
totals.sort_by(&:last).reverse.to_h
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
# ======================================================
|
4
|
+
# == [ Sidekiq ]
|
5
|
+
# ======================================================
|
6
|
+
def sidekiq_avg_duration_series
|
7
|
+
query = [
|
8
|
+
'sidekiq/current',
|
9
|
+
'--slice=time,class,duration_s',
|
10
|
+
'--duration_s>=1'
|
11
|
+
].join(' ')
|
12
|
+
|
13
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
14
|
+
|
15
|
+
method_key = :class
|
16
|
+
duration_key = :duration_s
|
17
|
+
|
18
|
+
# Collect Total by Duration
|
19
|
+
top = results.each_with_object({}) do |v, obj|
|
20
|
+
obj[v[method_key]] ||= []
|
21
|
+
obj[v[method_key]] << v[duration_key]
|
22
|
+
|
23
|
+
obj
|
24
|
+
end
|
25
|
+
|
26
|
+
# Average / Round
|
27
|
+
top.transform_values! { |x| (x.sum / x.size.to_f).round(1) }
|
28
|
+
|
29
|
+
# Only top by duration
|
30
|
+
method_calls = top_method_calls(top)
|
31
|
+
results.select! { |x| method_calls.any? x[method_key] }
|
32
|
+
|
33
|
+
build_group_time_avg(results, method_key, duration_key) # 5 Minutes
|
34
|
+
end
|
35
|
+
|
36
|
+
def sidekiq_duration
|
37
|
+
query = [
|
38
|
+
'sidekiq/current',
|
39
|
+
'--slice=time,duration_s',
|
40
|
+
'--exists=duration_s',
|
41
|
+
'--duration_s!=0',
|
42
|
+
'--exact'
|
43
|
+
].join(' ')
|
44
|
+
|
45
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
46
|
+
|
47
|
+
build_percentile_list(results, :duration_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
def sidekiq_errors_series
|
51
|
+
query = [
|
52
|
+
'sidekiq/current',
|
53
|
+
'--slice=time,class',
|
54
|
+
'--exists=errors'
|
55
|
+
].join(' ')
|
56
|
+
|
57
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
58
|
+
|
59
|
+
build_group_time_total(results, :class)
|
60
|
+
end
|
61
|
+
|
62
|
+
def sidekiq_job_latency
|
63
|
+
query = [
|
64
|
+
'sidekiq/current',
|
65
|
+
'--slice=time,class,scheduling_latency_s',
|
66
|
+
'--exists=scheduling_latency_s'
|
67
|
+
].join(' ')
|
68
|
+
|
69
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
70
|
+
|
71
|
+
build_percentile_list(results, :scheduling_latency_s)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
# Time / Percentile without Grouping
|
4
|
+
def build_time_percentile(results, sum, percentile = 0.90, interval = 5.minutes)
|
5
|
+
index = build_time_index(results, interval)
|
6
|
+
|
7
|
+
index.transform_values! { |_y| [] }
|
8
|
+
|
9
|
+
results.each do |r|
|
10
|
+
index[r.time.floor(interval)] << r[sum]
|
11
|
+
end
|
12
|
+
|
13
|
+
index.transform_values! do |l|
|
14
|
+
l.percentile(percentile)
|
15
|
+
end
|
16
|
+
|
17
|
+
index
|
18
|
+
end
|
19
|
+
|
20
|
+
# Time / Min/Max without Grouping
|
21
|
+
def build_time_method(results, sum, method = :max, interval = 5.minutes)
|
22
|
+
index = build_time_index(results, interval)
|
23
|
+
|
24
|
+
index.transform_values! { |_y| [] }
|
25
|
+
|
26
|
+
results.each do |r|
|
27
|
+
index[r.time.floor(interval)] << r[sum]
|
28
|
+
end
|
29
|
+
|
30
|
+
index.transform_values! do |l|
|
31
|
+
next if l.empty?
|
32
|
+
|
33
|
+
l.send(method)
|
34
|
+
end
|
35
|
+
|
36
|
+
index
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper to make rounding Values Easier
|
40
|
+
def round_values(results, round_value = 1)
|
41
|
+
results.transform_values { |x| x.round(round_value) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Helper to grab the most common desired results
|
45
|
+
def build_percentile_list(results, key, round_value = 1)
|
46
|
+
p99 = build_time_percentile(results, key, 0.99)
|
47
|
+
p95 = build_time_percentile(results, key, 0.95)
|
48
|
+
max = build_time_method(results, key, :max)
|
49
|
+
mean = build_time_method(results, key, :mean)
|
50
|
+
[
|
51
|
+
{ name: 'p99', data: round_values(p99, round_value) },
|
52
|
+
{ name: 'p95', data: round_values(p95, round_value) },
|
53
|
+
{ name: 'mean', data: round_values(mean, round_value) },
|
54
|
+
{ name: 'max', data: round_values(max, round_value) }
|
55
|
+
]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# May be worth adding other stats at some point
|
60
|
+
# p99 = build_time_percentile(results, :duration_ms, 0.99)
|
61
|
+
# p95 = build_time_percentile(results, :duration_ms, 0.95)
|
62
|
+
# min = build_time_method(results, :duration_ms, :min)
|
63
|
+
# max = build_time_method(results, :duration_ms, :max)
|
64
|
+
# median = build_time_method(results, :duration_ms, :median)
|
65
|
+
# mean = build_time_method(results, :duration_ms, :mean)
|
66
|
+
|
67
|
+
# [
|
68
|
+
# { name: 'p99', data: round_values(p99) },
|
69
|
+
# { name: 'p95', data: round_values(p95) },
|
70
|
+
# { name: 'median', data: median },
|
71
|
+
# { name: 'mean', data: round_values(mean) },
|
72
|
+
# { name: 'min', data: min },
|
73
|
+
# { name: 'max', data: max }
|
74
|
+
# ]
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Time Series Helpers
|
2
|
+
module TimeHelpers
|
3
|
+
def time_series
|
4
|
+
results = GreenHat::ShellHelper.filter_internal(params[:query])
|
5
|
+
|
6
|
+
# Prevent exceptions on empty results
|
7
|
+
return [] if results.empty?
|
8
|
+
|
9
|
+
# Ensure Time
|
10
|
+
results.select! { |x| x.key?(:time) && !x[:time].nil? }
|
11
|
+
|
12
|
+
if !params[:avg].blank?
|
13
|
+
build_group_time_avg(results, group_sym, avg_sym, param_interval)
|
14
|
+
elsif !params[:group].blank?
|
15
|
+
build_group_time_total(results, group_sym, param_interval)
|
16
|
+
else
|
17
|
+
build_time_total(results, param_interval)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def param_interval
|
22
|
+
if params[:interval]
|
23
|
+
params[:interval].to_i.minutes
|
24
|
+
else
|
25
|
+
5.minutes
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def group_sym
|
30
|
+
params[:group].strip.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
def avg_sym
|
34
|
+
params[:avg].strip.to_sym
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# General Web Helper
|
2
|
+
module WebHelpers
|
3
|
+
# ======================================================
|
4
|
+
# == [ API ]
|
5
|
+
# ======================================================
|
6
|
+
def workhorse_request_total
|
7
|
+
query = [
|
8
|
+
'gitlab-workhorse/current',
|
9
|
+
'--slice=time,status',
|
10
|
+
'--exists=status'
|
11
|
+
].join(' ')
|
12
|
+
|
13
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
14
|
+
|
15
|
+
build_group_time_total(results, :status)
|
16
|
+
end
|
17
|
+
|
18
|
+
def workhorse_client_total
|
19
|
+
query = [
|
20
|
+
'gitlab-workhorse/current',
|
21
|
+
'--slice=user_agent',
|
22
|
+
'--exists=user_agent'
|
23
|
+
].join(' ')
|
24
|
+
|
25
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
26
|
+
|
27
|
+
build_count_by_occurance(results, :user_agent, 5)
|
28
|
+
end
|
29
|
+
|
30
|
+
def workhorse_duration
|
31
|
+
query = [
|
32
|
+
'gitlab-workhorse/current',
|
33
|
+
'--slice=time,duration_ms',
|
34
|
+
'--exists=duration_ms',
|
35
|
+
'--duration_ms!=0',
|
36
|
+
'--exact'
|
37
|
+
].join(' ')
|
38
|
+
|
39
|
+
results = GreenHat::ShellHelper.filter_internal(query)
|
40
|
+
|
41
|
+
build_percentile_list(results, :duration_ms)
|
42
|
+
end
|
43
|
+
end
|
data/lib/greenhat/web.rb
CHANGED
@@ -1,46 +1,90 @@
|
|
1
1
|
require 'sinatra/base'
|
2
2
|
|
3
|
+
# Chart Shims
|
4
|
+
# https://github.com/ankane/chartkick/blob/master/lib/chartkick.rb
|
5
|
+
require 'chartkick/enumerable'
|
6
|
+
require 'chartkick/helper'
|
7
|
+
require 'chartkick/version'
|
8
|
+
require 'chartkick/sinatra'
|
9
|
+
|
10
|
+
# Load Web Helpers
|
11
|
+
require_all "#{File.dirname(__FILE__)}/web"
|
12
|
+
|
3
13
|
# Helper
|
4
14
|
module GreenHat
|
5
15
|
# Web Helper
|
6
16
|
module Web
|
7
17
|
# Template Helper
|
8
18
|
class MyApp < Sinatra::Base
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
19
|
+
helpers Chartkick::Helper
|
20
|
+
helpers ::WebHelpers
|
21
|
+
helpers ::TimeHelpers
|
22
|
+
|
23
|
+
configure do
|
24
|
+
set :server, :puma
|
25
|
+
set :port, 4567
|
26
|
+
set :quiet, true
|
27
|
+
set :server_settings, Silent: true
|
28
|
+
end
|
13
29
|
|
14
30
|
get '/' do
|
15
|
-
|
31
|
+
slim :index
|
16
32
|
end
|
17
|
-
end
|
18
33
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# original_stdout = $stdout.clone
|
23
|
-
# $stderr.reopen(File.new('/dev/null', 'w'))
|
24
|
-
# $stdout.reopen(File.new('/dev/null', 'w'))
|
34
|
+
get '/chart' do
|
35
|
+
slim :chart
|
36
|
+
end
|
25
37
|
|
26
|
-
|
27
|
-
#
|
28
|
-
|
38
|
+
get '/charts/:chart_type' do
|
39
|
+
# TODO: Better validation?
|
40
|
+
slim params[:chart_type].to_sym
|
41
|
+
end
|
29
42
|
|
30
|
-
|
43
|
+
get '/chart/time' do
|
44
|
+
@index = time_series unless params[:query].empty?
|
45
|
+
slim :time
|
46
|
+
end
|
31
47
|
|
32
|
-
|
48
|
+
get '/chart/chartkick.js' do
|
49
|
+
content_type 'application/javascript'
|
50
|
+
File.read("#{File.dirname(__FILE__)}/views/chartkick.js")
|
33
51
|
end
|
52
|
+
end
|
34
53
|
|
35
|
-
|
54
|
+
def self.setup
|
55
|
+
Archive.all.map(&:host_create).map(&:save!)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.toggle
|
59
|
+
if alive?
|
60
|
+
LogBot.info('Stopping Web')
|
61
|
+
stop
|
62
|
+
else
|
63
|
+
start
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.start
|
68
|
+
setup if Host.count.zero?
|
69
|
+
|
70
|
+
return true if alive? # Prevent Dupes
|
71
|
+
|
72
|
+
LogBot.info('Starting Web')
|
73
|
+
|
74
|
+
@thread = Thread.new { MyApp.run! }
|
75
|
+
# MyApp.run! # Debugging
|
36
76
|
end
|
37
77
|
|
38
78
|
def self.thread
|
39
79
|
@thread
|
40
80
|
end
|
41
81
|
|
82
|
+
def self.alive?
|
83
|
+
!thread.nil? && thread.alive?
|
84
|
+
end
|
85
|
+
|
42
86
|
def self.stop
|
43
|
-
thread.kill if
|
87
|
+
thread.kill if alive?
|
44
88
|
end
|
45
89
|
end
|
46
90
|
end
|