rails_observatory 0.1.0 → 0.1.1
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 +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: []
|