rails_observatory 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -3
- data/app/assets/js/controllers/chart_controller.js +26 -15
- data/app/helpers/rails_observatory/application_helper.rb +7 -2
- data/app/views/rails_observatory/requests/index.html.erb +2 -3
- data/lib/rails_observatory/middleware.rb +5 -1
- data/lib/rails_observatory/models/event_collection.rb +7 -0
- data/lib/rails_observatory/redis/time_series/query_builder.rb +3 -2
- data/lib/rails_observatory/redis/time_series/timing_script.lua +10 -7
- data/lib/rails_observatory/serializers/serializer.rb +2 -2
- data/lib/rails_observatory/version.rb +1 -1
- data/public/assets/js/application.js +17 -12
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 493d740aa5e9922dd611aa3425c2830c4cfaa920a3474d7d58383613cabdaf05
|
4
|
+
data.tar.gz: 982d077e561ce0895f6c722d690d9aad3ceaf79302d302dfb7afb544bc07a3bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af0cdab142f36284b8265a1953a4a1663ebf86f267be6854a50a6e61ab213bc5067f38159c61095f1e4ddae5b191e995fac6e902f9febc7c494a81b854555f67
|
7
|
+
data.tar.gz: 3e38d9ba1e846b8648eeb714dbe89c39fcca3a216ec050dcda1cebf09a3d19b059b1949013ce8ecc4a5afec018aee1114648f00f42642f3491b4f38ec1f082d1
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Rails Observatory
|
2
2
|
|
3
|
-
|
3
|
+
Observability for your Rails app.
|
4
4
|
|
5
5
|
Rails observatory hooks into ActiveSupport::Instrumentation
|
6
6
|
with [RedisStack](https://redis.io/docs/about/about-stack/) to provide
|
@@ -38,5 +38,4 @@ Pull requests, issues and more are welcome!
|
|
38
38
|
|
39
39
|
## License
|
40
40
|
|
41
|
-
The gem is available as open source under the terms of the
|
42
|
-
If you'd like a commercial license, please contact me mark.godwin@hey.com
|
41
|
+
The gem is available as open source under the terms of the MIT License.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import {Controller} from "@hotwired/stimulus";
|
2
2
|
import Apexcharts from "apexcharts";
|
3
3
|
|
4
|
-
const icicleChartOptions = function(controller) {
|
4
|
+
const icicleChartOptions = function (controller) {
|
5
5
|
return {
|
6
6
|
chart: {
|
7
7
|
id: controller.element.id,
|
@@ -9,10 +9,12 @@ const icicleChartOptions = function(controller) {
|
|
9
9
|
type: 'rangeBar',
|
10
10
|
height: 300,
|
11
11
|
events: {
|
12
|
-
dataPointSelection: function(event, chartContext, config) {
|
13
|
-
controller.dispatch('selected', {
|
12
|
+
dataPointSelection: function (event, chartContext, config) {
|
13
|
+
controller.dispatch('selected', {
|
14
|
+
detail: {
|
14
15
|
...config.w.config.series[config.seriesIndex].data[config.dataPointIndex]
|
15
|
-
}
|
16
|
+
}
|
17
|
+
})
|
16
18
|
}
|
17
19
|
},
|
18
20
|
},
|
@@ -69,8 +71,12 @@ const icicleChartOptions = function(controller) {
|
|
69
71
|
const seriesSelfTime = controller.series()[opts.seriesIndex].data.reduce((acc, val) => {
|
70
72
|
return acc + val['event_self_time']
|
71
73
|
}, 0)
|
72
|
-
console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
|
73
|
-
const totalSelfTime = controller.series().reduce((acc, val) => {
|
74
|
+
// console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
|
75
|
+
const totalSelfTime = controller.series().reduce((acc, val) => {
|
76
|
+
return acc + val.data.reduce((acc, val) => {
|
77
|
+
return acc + val['event_self_time']
|
78
|
+
}, 0)
|
79
|
+
}, 0)
|
74
80
|
const percent = seriesSelfTime / totalSelfTime * 100
|
75
81
|
return `${seriesName} <span class="percent">${seriesSelfTime.toFixed(1)}ms <div class="bar"><div style="width:${percent.toFixed(1)}%"></div></div></span> `
|
76
82
|
},
|
@@ -97,7 +103,7 @@ const icicleChartOptions = function(controller) {
|
|
97
103
|
|
98
104
|
}
|
99
105
|
|
100
|
-
const defaultOptions = function(controller) {
|
106
|
+
const defaultOptions = function (controller) {
|
101
107
|
return {
|
102
108
|
chart: {
|
103
109
|
id: controller.element.id,
|
@@ -126,14 +132,6 @@ const defaultOptions = function(controller) {
|
|
126
132
|
opacityTo: 0
|
127
133
|
}
|
128
134
|
},
|
129
|
-
theme: {
|
130
|
-
monochrome: {
|
131
|
-
enabled: true,
|
132
|
-
color: '#0c8be8',
|
133
|
-
shadeTo: 'dark',
|
134
|
-
shadeIntensity: 0.65
|
135
|
-
}
|
136
|
-
},
|
137
135
|
stroke: {
|
138
136
|
width: 2,
|
139
137
|
curve: 'straight'
|
@@ -142,6 +140,17 @@ const defaultOptions = function(controller) {
|
|
142
140
|
|
143
141
|
}
|
144
142
|
|
143
|
+
function stackedAreaOptions(controller) {
|
144
|
+
return {
|
145
|
+
...defaultOptions(controller),
|
146
|
+
chart: {
|
147
|
+
...defaultOptions(controller).chart,
|
148
|
+
type: 'area',
|
149
|
+
stacked: true,
|
150
|
+
},
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
145
154
|
export class ChartController extends Controller {
|
146
155
|
static targets = ["chart", "data"]
|
147
156
|
|
@@ -157,6 +166,8 @@ export class ChartController extends Controller {
|
|
157
166
|
switch (this.typeValue) {
|
158
167
|
case 'icicle':
|
159
168
|
return icicleChartOptions(this)
|
169
|
+
case 'stackedArea':
|
170
|
+
return stackedAreaOptions(this)
|
160
171
|
default:
|
161
172
|
return defaultOptions(this)
|
162
173
|
}
|
@@ -14,8 +14,13 @@ module RailsObservatory
|
|
14
14
|
html.to_html
|
15
15
|
end
|
16
16
|
|
17
|
-
def series_for(name:, aggregate_using:, time_range: nil, downsample:
|
18
|
-
|
17
|
+
def series_for(name:, aggregate_using:, time_range: nil, downsample: 60, children: false, **opts)
|
18
|
+
if children
|
19
|
+
series = TimeSeries.where(parent: name, **opts)
|
20
|
+
else
|
21
|
+
series = TimeSeries.where(name:, **opts)
|
22
|
+
end
|
23
|
+
series = series.downsample(downsample, using: aggregate_using)
|
19
24
|
series = series.slice(time_range) if time_range
|
20
25
|
series
|
21
26
|
end
|
@@ -11,9 +11,8 @@
|
|
11
11
|
<div class="layout-requests_index-chart">
|
12
12
|
<%= render 'chart', name: 'Requests', type: 'bar', series: series_for(name: 'request.count', aggregate_using: :sum, action: params[:controller_action]) %>
|
13
13
|
<%= render 'chart', name: 'Latency', type: 'area', series: series_for(name: 'request.latency', aggregate_using: :avg, action: params[:controller_action]) %>
|
14
|
-
|
15
|
-
|
16
|
-
<%#= render 'chart', name: 'Errors', series: @errors, type: 'bar', palette: 'palette7' %>
|
14
|
+
<%= render 'chart', name: "Latency Breakdown", series: series_for(name: 'request.latency', aggregate_using: :avg, compaction: :avg, action: params[:controller_action], children: true), type: 'stackedArea' %>
|
15
|
+
<%= render 'chart', name: 'Errors', series: series_for(name: 'request.error_count', aggregate_using: :sum, action: params[:controller_action]), type: 'bar' %>
|
17
16
|
</div>
|
18
17
|
<% if params[:controller_action].blank? %>
|
19
18
|
<section class="layout-requests_index-by_controller">
|
@@ -48,6 +48,7 @@ module RailsObservatory
|
|
48
48
|
status, headers, body = response
|
49
49
|
body = ::Rack::BodyProxy.new(body) do
|
50
50
|
duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_at_mono)
|
51
|
+
serialized_events = events.map { Serializer.serialize(_1) }
|
51
52
|
RequestTrace.new(
|
52
53
|
request_id: request.request_id,
|
53
54
|
status:,
|
@@ -59,13 +60,16 @@ module RailsObservatory
|
|
59
60
|
duration:,
|
60
61
|
time: start_at.to_f,
|
61
62
|
path: request.path,
|
62
|
-
events:
|
63
|
+
events: serialized_events,
|
63
64
|
logs:
|
64
65
|
).save
|
65
66
|
labels = { action: controller_action, format: request.format, status:, http_method: request.method }
|
66
67
|
TimeSeries.record_occurrence("request.count", labels:)
|
67
68
|
TimeSeries.record_occurrence("request.error_count", labels:) if status >= 500
|
68
69
|
TimeSeries.record_timing("request.latency", duration, labels:)
|
70
|
+
EventCollection.new(serialized_events).self_time_by_library.each do |library, self_time|
|
71
|
+
TimeSeries.record_timing("request.latency/#{library}", self_time, labels: { action: controller_action })
|
72
|
+
end
|
69
73
|
rescue => e
|
70
74
|
puts e
|
71
75
|
puts e.backtrace.join("\n")
|
@@ -127,10 +127,11 @@ module RailsObservatory
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def ts_filters
|
130
|
-
|
130
|
+
root_labels = @conditions[:name] || @conditions[:parent]
|
131
|
+
raise 'Must specify name or parent' if @conditions[:name].blank? && @conditions[:parent].blank?
|
131
132
|
|
132
133
|
@conditions[@group] = "*" if @group
|
133
|
-
labels = @series_class.redis.call('SMEMBERS', "#{
|
134
|
+
labels = @series_class.redis.call('SMEMBERS', "#{root_labels}:labels")
|
134
135
|
labels = labels.map { |l| [l.to_sym, nil] }.to_h
|
135
136
|
@conditions.reverse_merge!(labels)
|
136
137
|
@conditions.map do |k, v|
|
@@ -9,7 +9,7 @@ local function generate_key_combinations(keys)
|
|
9
9
|
for i = start_idx, n do
|
10
10
|
local new_comb = {}
|
11
11
|
for _, v in ipairs(curr_comb) do
|
12
|
-
|
12
|
+
table.insert(new_comb, v)
|
13
13
|
end
|
14
14
|
table.insert(new_comb, keys[i])
|
15
15
|
table.insert(combs, new_comb)
|
@@ -36,7 +36,7 @@ local metric_name = tostring(ARGV[1]) -- Ensure it's a string
|
|
36
36
|
local value_to_add = tonumber(ARGV[2]) -- Ensure it's a number
|
37
37
|
local raw_retention = 10000 -- Hardcoded to 10ms
|
38
38
|
local compaction_retention = 31536000000 -- Hardcoded to 1 year in ms (365*24*60*60*1000)
|
39
|
-
local compactions = {"avg", "min", "max"}
|
39
|
+
local compactions = { "avg", "min", "max" }
|
40
40
|
|
41
41
|
-- Assuming base_name is defined somewhere above
|
42
42
|
local parent_label = extract_parent_label(metric_name)
|
@@ -44,11 +44,15 @@ local parent_label = extract_parent_label(metric_name)
|
|
44
44
|
-- Extracting labels
|
45
45
|
local labels = {}
|
46
46
|
local keys = {}
|
47
|
-
for i=3, #ARGV, 2 do
|
47
|
+
for i = 3, #ARGV, 2 do
|
48
48
|
local key = tostring(ARGV[i])
|
49
|
-
local value = tostring(ARGV[i+1])
|
49
|
+
local value = tostring(ARGV[i + 1])
|
50
50
|
labels[key] = value
|
51
|
-
|
51
|
+
if parent_label then
|
52
|
+
redis.call("SADD", parent_label .. ':labels', key)
|
53
|
+
else
|
54
|
+
redis.call("SADD", metric_name .. ':labels', key)
|
55
|
+
end
|
52
56
|
table.insert(keys, key)
|
53
57
|
end
|
54
58
|
|
@@ -78,9 +82,8 @@ for _, comb_keys in ipairs(key_combinations) do
|
|
78
82
|
for _, compaction in ipairs(compactions) do
|
79
83
|
local compaction_key = ts_name .. "_" .. compaction
|
80
84
|
if redis.call("EXISTS", compaction_key) == 0 then
|
81
|
-
redis.call("TS.CREATE", compaction_key, "RETENTION", compaction_retention, "CHUNK_SIZE", 48, "LABELS","name", metric_name, "compaction", compaction, unpack(label_set))
|
85
|
+
redis.call("TS.CREATE", compaction_key, "RETENTION", compaction_retention, "CHUNK_SIZE", 48, "LABELS", "name", metric_name, "compaction", compaction, unpack(label_set))
|
82
86
|
redis.call("TS.CREATERULE", ts_name, compaction_key, "AGGREGATION", compaction, 10000)
|
83
|
-
return redis.call("TS.INFO", compaction_key)
|
84
87
|
end
|
85
88
|
end
|
86
89
|
redis.call("TS.ADD", ts_name, "*", value_to_add)
|
@@ -34,7 +34,7 @@ module RailsObservatory
|
|
34
34
|
when Symbol
|
35
35
|
argument.to_s
|
36
36
|
else
|
37
|
-
ADDITIONAL_SERIALIZERS.find { argument.is_a?(_1.klass) }&.new&.serialize(argument) || "Unable to serialize #{argument.class.name}"
|
37
|
+
ADDITIONAL_SERIALIZERS.find { argument.is_a?(_1.klass) }&.new&.serialize(argument)&.deep_stringify_keys || "Unable to serialize #{argument.class.name}"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -42,7 +42,7 @@ module RailsObservatory
|
|
42
42
|
argument.each_with_object({}) do |(key, value), hash|
|
43
43
|
case key
|
44
44
|
when String, Symbol
|
45
|
-
hash[key] = serialize_payload(value)
|
45
|
+
hash[key.to_s] = serialize_payload(value)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
@@ -10850,9 +10850,11 @@
|
|
10850
10850
|
height: 300,
|
10851
10851
|
events: {
|
10852
10852
|
dataPointSelection: function(event, chartContext, config) {
|
10853
|
-
controller.dispatch("selected", {
|
10854
|
-
|
10855
|
-
|
10853
|
+
controller.dispatch("selected", {
|
10854
|
+
detail: {
|
10855
|
+
...config.w.config.series[config.seriesIndex].data[config.dataPointIndex]
|
10856
|
+
}
|
10857
|
+
});
|
10856
10858
|
}
|
10857
10859
|
}
|
10858
10860
|
},
|
@@ -10903,7 +10905,6 @@
|
|
10903
10905
|
const seriesSelfTime = controller.series()[opts.seriesIndex].data.reduce((acc, val) => {
|
10904
10906
|
return acc + val["event_self_time"];
|
10905
10907
|
}, 0);
|
10906
|
-
console.log(controller.series()[opts.seriesIndex].name, controller.series()[opts.seriesIndex].data);
|
10907
10908
|
const totalSelfTime = controller.series().reduce((acc, val) => {
|
10908
10909
|
return acc + val.data.reduce((acc2, val2) => {
|
10909
10910
|
return acc2 + val2["event_self_time"];
|
@@ -10962,20 +10963,22 @@
|
|
10962
10963
|
opacityTo: 0
|
10963
10964
|
}
|
10964
10965
|
},
|
10965
|
-
theme: {
|
10966
|
-
monochrome: {
|
10967
|
-
enabled: true,
|
10968
|
-
color: "#0c8be8",
|
10969
|
-
shadeTo: "dark",
|
10970
|
-
shadeIntensity: 0.65
|
10971
|
-
}
|
10972
|
-
},
|
10973
10966
|
stroke: {
|
10974
10967
|
width: 2,
|
10975
10968
|
curve: "straight"
|
10976
10969
|
}
|
10977
10970
|
};
|
10978
10971
|
};
|
10972
|
+
function stackedAreaOptions(controller) {
|
10973
|
+
return {
|
10974
|
+
...defaultOptions(controller),
|
10975
|
+
chart: {
|
10976
|
+
...defaultOptions(controller).chart,
|
10977
|
+
type: "area",
|
10978
|
+
stacked: true
|
10979
|
+
}
|
10980
|
+
};
|
10981
|
+
}
|
10979
10982
|
var ChartController = class extends Controller {
|
10980
10983
|
static targets = ["chart", "data"];
|
10981
10984
|
static values = {
|
@@ -10989,6 +10992,8 @@
|
|
10989
10992
|
switch (this.typeValue) {
|
10990
10993
|
case "icicle":
|
10991
10994
|
return icicleChartOptions(this);
|
10995
|
+
case "stackedArea":
|
10996
|
+
return stackedAreaOptions(this);
|
10992
10997
|
default:
|
10993
10998
|
return defaultOptions(this);
|
10994
10999
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_observatory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Godwin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faker
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
description:
|
69
|
+
description: See what's happening in your Rails App
|
70
70
|
email:
|
71
71
|
- mark.godwin@hey.com
|
72
72
|
executables: []
|
@@ -193,5 +193,5 @@ requirements: []
|
|
193
193
|
rubygems_version: 3.4.13
|
194
194
|
signing_key:
|
195
195
|
specification_version: 4
|
196
|
-
summary:
|
196
|
+
summary: See what's happening in your Rails App
|
197
197
|
test_files: []
|