solid_observer 0.3.0 → 0.5.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/CHANGELOG.md +34 -0
- data/README.md +195 -82
- data/app/assets/javascripts/solid_observer/live_poll.js +3 -1
- data/app/controllers/solid_observer/application_controller.rb +1 -0
- data/app/controllers/solid_observer/cable_dashboard_controller.rb +52 -0
- data/app/controllers/solid_observer/cable_operations_controller.rb +16 -0
- data/app/controllers/solid_observer/cache_dashboard_controller.rb +52 -0
- data/app/controllers/solid_observer/cache_operations_controller.rb +24 -0
- data/app/controllers/solid_observer/dashboard_controller.rb +38 -1
- data/app/controllers/solid_observer/storages_controller.rb +1 -1
- data/app/helpers/solid_observer/application_helper.rb +268 -5
- data/app/helpers/solid_observer/dashboard_helper.rb +30 -11
- data/app/models/solid_observer/cable_event.rb +13 -0
- data/app/models/solid_observer/cable_metric.rb +12 -0
- data/app/models/solid_observer/cache_event.rb +15 -0
- data/app/models/solid_observer/cache_metric.rb +13 -0
- data/app/models/solid_observer/storage_info.rb +4 -1
- data/app/views/layouts/solid_observer/application.html.erb +157 -19
- data/app/views/solid_observer/cable_dashboard/_charts.html.erb +31 -0
- data/app/views/solid_observer/cable_dashboard/_recent_events.html.erb +34 -0
- data/app/views/solid_observer/cable_dashboard/_summary.html.erb +34 -0
- data/app/views/solid_observer/cable_dashboard/index.html.erb +118 -0
- data/app/views/solid_observer/cache_dashboard/_charts.html.erb +40 -0
- data/app/views/solid_observer/cache_dashboard/_recent_events.html.erb +34 -0
- data/app/views/solid_observer/cache_dashboard/_summary.html.erb +39 -0
- data/app/views/solid_observer/cache_dashboard/index.html.erb +62 -0
- data/app/views/solid_observer/cache_operations/_confirm_clear.html.erb +6 -0
- data/app/views/solid_observer/cache_operations/index.html.erb +60 -0
- data/app/views/solid_observer/dashboard/_queue_table.html.erb +1 -0
- data/app/views/solid_observer/dashboard/index.html.erb +32 -5
- data/app/views/solid_observer/events/index.html.erb +1 -0
- data/app/views/solid_observer/jobs/index.html.erb +1 -0
- data/app/views/solid_observer/jobs/show.html.erb +3 -3
- data/app/views/solid_observer/storages/show.html.erb +90 -32
- data/config/routes.rb +7 -0
- data/db/migrate/20260601000001_create_solid_observer_cache_events.rb +22 -0
- data/db/migrate/20260601000002_create_solid_observer_cache_metrics.rb +18 -0
- data/db/migrate/20260602000001_add_component_to_solid_observer_storage_infos.rb +8 -0
- data/db/migrate/20260612000001_add_event_type_recorded_at_index_to_cache_events.rb +21 -0
- data/db/migrate/20260619000001_create_solid_observer_cable_events.rb +22 -0
- data/db/migrate/20260619000002_create_solid_observer_cable_metrics.rb +17 -0
- data/lib/generators/solid_observer/install_generator.rb +8 -1
- data/lib/generators/solid_observer/templates/initializer.rb.tt +20 -4
- data/lib/solid_observer/base_event.rb +1 -1
- data/lib/solid_observer/base_metric.rb +1 -1
- data/lib/solid_observer/base_record.rb +8 -0
- data/lib/solid_observer/cable_event_buffer.rb +28 -0
- data/lib/solid_observer/cable_metric_buffer.rb +230 -0
- data/lib/solid_observer/cable_subscriber.rb +57 -0
- data/lib/solid_observer/cache_event_buffer.rb +28 -0
- data/lib/solid_observer/cache_metric_buffer.rb +229 -0
- data/lib/solid_observer/cache_subscriber.rb +47 -0
- data/lib/solid_observer/chart_buffer.rb +84 -27
- data/lib/solid_observer/cli/storage.rb +16 -13
- data/lib/solid_observer/configuration.rb +67 -5
- data/lib/solid_observer/engine.rb +70 -15
- data/lib/solid_observer/event_buffer_core.rb +218 -0
- data/lib/solid_observer/queue_event_buffer.rb +9 -201
- data/lib/solid_observer/services/cable_operations.rb +74 -0
- data/lib/solid_observer/services/cable_stats.rb +385 -0
- data/lib/solid_observer/services/cache_operations.rb +115 -0
- data/lib/solid_observer/services/cache_stats.rb +346 -0
- data/lib/solid_observer/services/cleanup_storage.rb +98 -47
- data/lib/solid_observer/services/database_size.rb +13 -8
- data/lib/solid_observer/services/flush_cable_event_buffer.rb +54 -0
- data/lib/solid_observer/services/flush_cable_metrics.rb +54 -0
- data/lib/solid_observer/services/flush_cache_event_buffer.rb +54 -0
- data/lib/solid_observer/services/flush_cache_metrics.rb +56 -0
- data/lib/solid_observer/services/record_cable_event.rb +114 -0
- data/lib/solid_observer/services/record_cable_metric.rb +73 -0
- data/lib/solid_observer/services/record_cache_event.rb +165 -0
- data/lib/solid_observer/services/record_cache_metric.rb +66 -0
- data/lib/solid_observer/services/storage_info_snapshot.rb +216 -0
- data/lib/solid_observer/version.rb +1 -1
- data/lib/solid_observer.rb +36 -11
- data/lib/tasks/solid_observer.rake +111 -21
- metadata +47 -5
- data/bin/console +0 -11
- data/bin/quality_gate +0 -95
- data/bin/setup +0 -8
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "database_size"
|
|
4
|
+
|
|
5
|
+
module SolidObserver
|
|
6
|
+
module Services
|
|
7
|
+
class StorageInfoSnapshot
|
|
8
|
+
class RecordCount
|
|
9
|
+
def initialize(connection, table_name)
|
|
10
|
+
@connection = connection
|
|
11
|
+
@table_name = table_name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def solid_cache_count
|
|
15
|
+
case adapter_key
|
|
16
|
+
when :postgresql then postgresql_approximate_count
|
|
17
|
+
when :mysql then mysql_approximate_count
|
|
18
|
+
else
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :connection, :table_name
|
|
26
|
+
|
|
27
|
+
def adapter_key
|
|
28
|
+
case connection.adapter_name.to_s.downcase
|
|
29
|
+
when /postgres|postgis/ then :postgresql
|
|
30
|
+
when "mysql2", "trilogy", "mysql" then :mysql
|
|
31
|
+
else
|
|
32
|
+
:other
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def postgresql_approximate_count
|
|
37
|
+
quoted_table = connection.quote(table_name)
|
|
38
|
+
|
|
39
|
+
connection.query_value(<<~SQL.squish)&.to_i || 0
|
|
40
|
+
SELECT COALESCE(
|
|
41
|
+
(SELECT reltuples::bigint FROM pg_class WHERE oid = to_regclass(#{quoted_table})),
|
|
42
|
+
0
|
|
43
|
+
)
|
|
44
|
+
SQL
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def mysql_approximate_count
|
|
48
|
+
quoted_table = connection.quote(table_name)
|
|
49
|
+
|
|
50
|
+
connection.query_value(<<~SQL)&.to_i || 0
|
|
51
|
+
SELECT COALESCE(table_rows, 0)
|
|
52
|
+
FROM information_schema.tables
|
|
53
|
+
WHERE table_schema = DATABASE()
|
|
54
|
+
AND table_name = #{quoted_table}
|
|
55
|
+
SQL
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
Component = Struct.new(:key, :label, :record_label, :model, :enabled, keyword_init: true) do
|
|
60
|
+
def enabled?
|
|
61
|
+
enabled.call
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def solid_cache?
|
|
65
|
+
key == "solid_cache"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def solid_cable?
|
|
69
|
+
key == "solid_cable"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def storage_model
|
|
73
|
+
model.call
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def snapshot
|
|
77
|
+
return unless enabled?
|
|
78
|
+
return unavailable_snapshot(reason: "SolidCache is unavailable") if unavailable_solid_cache?
|
|
79
|
+
return unavailable_snapshot(reason: "SolidCable is unavailable") if solid_cable? && !defined?(::SolidCable::Message)
|
|
80
|
+
|
|
81
|
+
existing_data_source_snapshot
|
|
82
|
+
rescue *StorageInfoSnapshot::CONNECTION_ERRORS, TypeError
|
|
83
|
+
unavailable_snapshot(reason: "Storage unavailable")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def available_snapshot(db_size_bytes:, event_count:, **extras)
|
|
87
|
+
{
|
|
88
|
+
component: key,
|
|
89
|
+
label: label,
|
|
90
|
+
available: true,
|
|
91
|
+
db_size_bytes: db_size_bytes,
|
|
92
|
+
event_count: event_count,
|
|
93
|
+
record_label: record_label,
|
|
94
|
+
recorded_at: Time.current,
|
|
95
|
+
unavailable_reason: nil
|
|
96
|
+
}.merge(extras)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def unavailable_snapshot(reason:)
|
|
100
|
+
{
|
|
101
|
+
component: key,
|
|
102
|
+
label: label,
|
|
103
|
+
available: false,
|
|
104
|
+
db_size_bytes: nil,
|
|
105
|
+
event_count: nil,
|
|
106
|
+
record_label: record_label,
|
|
107
|
+
recorded_at: nil,
|
|
108
|
+
unavailable_reason: reason
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def unavailable_solid_cache?
|
|
115
|
+
solid_cache? && !defined?(::SolidCache::Entry)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def existing_data_source_snapshot
|
|
119
|
+
record_model = storage_model
|
|
120
|
+
connection = record_model.connection
|
|
121
|
+
table_name = record_model.table_name.to_s
|
|
122
|
+
return unavailable_snapshot(reason: "Table unavailable") unless data_source_available?(connection, table_name)
|
|
123
|
+
|
|
124
|
+
build_available_snapshot(record_model, connection, table_name)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def build_available_snapshot(record_model, connection, table_name)
|
|
128
|
+
count = -> { record_model.count }
|
|
129
|
+
event_count = solid_cache? ? RecordCount.new(connection, table_name).solid_cache_count(&count) : count.call
|
|
130
|
+
|
|
131
|
+
available_snapshot(
|
|
132
|
+
db_size_bytes: DatabaseSize.call(connection: connection, table_name: table_name),
|
|
133
|
+
event_count: event_count,
|
|
134
|
+
**extras_for(record_model)
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def extras_for(record_model)
|
|
139
|
+
solid_cable? ? solid_cable_extras(record_model) : {}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def data_source_available?(connection, table_name)
|
|
143
|
+
!table_name.empty? && connection.data_source_exists?(table_name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def solid_cable_extras(record_model)
|
|
147
|
+
{
|
|
148
|
+
trimmable_count: safe_query { record_model.trimmable.count },
|
|
149
|
+
oldest_message_age_seconds: safe_query { (Time.current - record_model.minimum(:created_at).to_time).to_i }
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def safe_query
|
|
154
|
+
yield
|
|
155
|
+
rescue *StorageInfoSnapshot::CONNECTION_ERRORS, TypeError, NoMethodError
|
|
156
|
+
nil
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
COMPONENTS = [
|
|
161
|
+
Component.new(
|
|
162
|
+
key: "queue_observer",
|
|
163
|
+
label: "Queue observer",
|
|
164
|
+
record_label: "observer events",
|
|
165
|
+
model: -> { SolidObserver::QueueEvent },
|
|
166
|
+
enabled: -> { SolidObserver.config.solid_queue_enabled? }
|
|
167
|
+
),
|
|
168
|
+
Component.new(
|
|
169
|
+
key: "cache_observer",
|
|
170
|
+
label: "Cache observer",
|
|
171
|
+
record_label: "observer events",
|
|
172
|
+
model: -> { SolidObserver::CacheEvent },
|
|
173
|
+
enabled: -> { SolidObserver.config.solid_cache_enabled? }
|
|
174
|
+
),
|
|
175
|
+
Component.new(
|
|
176
|
+
key: "solid_cache",
|
|
177
|
+
label: "SolidCache",
|
|
178
|
+
record_label: "cache rows",
|
|
179
|
+
model: -> { ::SolidCache::Entry },
|
|
180
|
+
enabled: -> { SolidObserver.config.solid_cache_enabled? }
|
|
181
|
+
),
|
|
182
|
+
Component.new(
|
|
183
|
+
key: "cable_observer",
|
|
184
|
+
label: "Cable telemetry",
|
|
185
|
+
record_label: "observer events",
|
|
186
|
+
model: -> { SolidObserver::CableEvent },
|
|
187
|
+
enabled: -> { SolidObserver.config.solid_cable_enabled? }
|
|
188
|
+
),
|
|
189
|
+
Component.new(
|
|
190
|
+
key: "solid_cable",
|
|
191
|
+
label: "Solid Cable messages",
|
|
192
|
+
record_label: "messages",
|
|
193
|
+
model: -> { ::SolidCable::Message },
|
|
194
|
+
enabled: -> { SolidObserver.config.solid_cable_enabled? }
|
|
195
|
+
)
|
|
196
|
+
].freeze
|
|
197
|
+
|
|
198
|
+
CONNECTION_ERRORS = [
|
|
199
|
+
ActiveRecord::ConnectionNotEstablished,
|
|
200
|
+
ActiveRecord::StatementInvalid,
|
|
201
|
+
*([PG::ConnectionBad] if defined?(PG::ConnectionBad)),
|
|
202
|
+
*([Mysql2::Error::ConnectionError] if defined?(Mysql2::Error::ConnectionError)),
|
|
203
|
+
*([Trilogy::Error] if defined?(Trilogy::Error)),
|
|
204
|
+
*([SQLite3::CantOpenException] if defined?(SQLite3::CantOpenException))
|
|
205
|
+
].freeze
|
|
206
|
+
|
|
207
|
+
def self.call
|
|
208
|
+
new.call
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def call
|
|
212
|
+
COMPONENTS.filter_map(&:snapshot)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
data/lib/solid_observer.rb
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/lazy_load_hooks"
|
|
4
|
+
|
|
3
5
|
require_relative "solid_observer/version"
|
|
4
6
|
require_relative "solid_observer/configuration"
|
|
5
7
|
require_relative "solid_observer/correlation_id_resolver"
|
|
6
|
-
require_relative "solid_observer/base_event" if defined?(ActiveRecord)
|
|
7
|
-
require_relative "solid_observer/base_metric" if defined?(ActiveRecord)
|
|
8
8
|
require_relative "solid_observer/params/jobs_filter"
|
|
9
9
|
require_relative "solid_observer/params/events_filter"
|
|
10
|
-
require_relative "solid_observer/queries/job_executions_query" if defined?(ActiveRecord)
|
|
11
|
-
require_relative "solid_observer/queries/events_query" if defined?(ActiveRecord)
|
|
12
|
-
require_relative "solid_observer/queries/execution_finder" if defined?(ActiveRecord)
|
|
13
|
-
require_relative "solid_observer/services/record_event" if defined?(ActiveRecord)
|
|
14
|
-
require_relative "solid_observer/services/flush_event_buffer" if defined?(ActiveRecord)
|
|
15
|
-
require_relative "solid_observer/services/cleanup_storage" if defined?(ActiveRecord)
|
|
16
10
|
require_relative "solid_observer/services/ui_auth_check"
|
|
17
|
-
require_relative "solid_observer/
|
|
18
|
-
require_relative "solid_observer/subscriber" if defined?(ActiveSupport)
|
|
11
|
+
require_relative "solid_observer/subscriber"
|
|
19
12
|
require_relative "solid_observer/cli/base"
|
|
20
13
|
require_relative "solid_observer/cli/status"
|
|
21
14
|
require_relative "solid_observer/cli/storage"
|
|
22
15
|
require_relative "solid_observer/cli/jobs"
|
|
23
16
|
require_relative "solid_observer/queue_stats"
|
|
24
|
-
require_relative "../app/presenters/solid_observer/execution_presenter"
|
|
25
17
|
require_relative "solid_observer/engine" if defined?(Rails::Engine)
|
|
26
18
|
|
|
19
|
+
ActiveSupport.on_load(:active_record) do
|
|
20
|
+
require_relative "solid_observer/base_record"
|
|
21
|
+
require_relative "solid_observer/base_event"
|
|
22
|
+
require_relative "solid_observer/base_metric"
|
|
23
|
+
require_relative "solid_observer/queries/job_executions_query"
|
|
24
|
+
require_relative "solid_observer/queries/events_query"
|
|
25
|
+
require_relative "solid_observer/queries/execution_finder"
|
|
26
|
+
require_relative "solid_observer/services/record_event"
|
|
27
|
+
require_relative "solid_observer/services/flush_event_buffer"
|
|
28
|
+
require_relative "solid_observer/services/cleanup_storage"
|
|
29
|
+
require_relative "solid_observer/queue_event_buffer"
|
|
30
|
+
require_relative "solid_observer/cache_event_buffer"
|
|
31
|
+
require_relative "solid_observer/services/flush_cache_metrics"
|
|
32
|
+
require_relative "solid_observer/cache_metric_buffer"
|
|
33
|
+
require_relative "solid_observer/cache_subscriber"
|
|
34
|
+
require_relative "solid_observer/services/record_cache_event"
|
|
35
|
+
require_relative "solid_observer/services/record_cache_metric"
|
|
36
|
+
require_relative "solid_observer/services/flush_cache_event_buffer"
|
|
37
|
+
require_relative "solid_observer/services/cache_stats"
|
|
38
|
+
require_relative "solid_observer/services/cache_operations"
|
|
39
|
+
require_relative "solid_observer/cable_event_buffer"
|
|
40
|
+
require_relative "solid_observer/services/flush_cable_metrics"
|
|
41
|
+
require_relative "solid_observer/cable_metric_buffer"
|
|
42
|
+
require_relative "solid_observer/cable_subscriber"
|
|
43
|
+
require_relative "solid_observer/services/record_cable_event"
|
|
44
|
+
require_relative "solid_observer/services/record_cable_metric"
|
|
45
|
+
require_relative "solid_observer/services/flush_cable_event_buffer"
|
|
46
|
+
require_relative "solid_observer/services/cable_stats"
|
|
47
|
+
require_relative "solid_observer/services/cable_operations"
|
|
48
|
+
end
|
|
49
|
+
|
|
27
50
|
module SolidObserver
|
|
51
|
+
autoload :ExecutionPresenter, File.expand_path("../app/presenters/solid_observer/execution_presenter", __dir__)
|
|
52
|
+
|
|
28
53
|
class Error < StandardError; end
|
|
29
54
|
|
|
30
55
|
class << self
|
|
@@ -40,31 +40,90 @@ namespace :solid_observer do
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
namespace :buffer do
|
|
43
|
-
desc "Flush the event
|
|
43
|
+
desc "Flush the event buffers to the database"
|
|
44
44
|
task flush: :environment do
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
buffers = [
|
|
46
|
+
{label: "queue events", buffer: SolidObserver::QueueEventBuffer.instance},
|
|
47
|
+
{label: "cache events", buffer: SolidObserver::CacheEventBuffer.instance},
|
|
48
|
+
{label: "cache metric buckets", buffer: SolidObserver::CacheMetricBuffer.instance}
|
|
49
|
+
]
|
|
50
|
+
buffer_sizes = buffers.to_h { |entry| [entry, entry[:buffer].size] }
|
|
51
|
+
|
|
52
|
+
if buffer_sizes.values.sum.zero?
|
|
53
|
+
puts "Buffers are empty, nothing to flush"
|
|
54
|
+
else
|
|
55
|
+
buffers.each do |entry|
|
|
56
|
+
size = buffer_sizes.fetch(entry)
|
|
57
|
+
next if size.zero?
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
puts "Flushing #{size} #{entry[:label]} from buffer..."
|
|
60
|
+
entry[:buffer].flush!
|
|
61
|
+
end
|
|
62
|
+
puts "✓ Buffers flushed successfully"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc "Clear the event buffers without flushing to database"
|
|
67
|
+
task clear: :environment do
|
|
68
|
+
buffers = [
|
|
69
|
+
{label: "queue events", buffer: SolidObserver::QueueEventBuffer.instance},
|
|
70
|
+
{label: "cache events", buffer: SolidObserver::CacheEventBuffer.instance},
|
|
71
|
+
{label: "cache metric buckets", buffer: SolidObserver::CacheMetricBuffer.instance}
|
|
72
|
+
]
|
|
73
|
+
buffer_sizes = buffers.to_h { |entry| [entry, entry[:buffer].size] }
|
|
74
|
+
|
|
75
|
+
if buffer_sizes.values.sum.zero?
|
|
76
|
+
puts "Buffers are already empty"
|
|
50
77
|
else
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
buffers.each do |entry|
|
|
79
|
+
size = buffer_sizes.fetch(entry)
|
|
80
|
+
next if size.zero?
|
|
81
|
+
|
|
82
|
+
entry[:buffer].clear
|
|
83
|
+
puts "✓ Cleared #{size} #{entry[:label]} from buffer (not saved to database)"
|
|
84
|
+
end
|
|
54
85
|
end
|
|
55
86
|
end
|
|
87
|
+
end
|
|
56
88
|
|
|
57
|
-
|
|
89
|
+
namespace :cache do
|
|
90
|
+
desc "Clear all SolidCache entries after confirmation"
|
|
58
91
|
task clear: :environment do
|
|
59
|
-
|
|
60
|
-
|
|
92
|
+
if !SolidObserver::Services::CacheOperations.available?
|
|
93
|
+
puts SolidObserver::Services::CacheOperations.unavailable_message
|
|
94
|
+
next
|
|
95
|
+
end
|
|
61
96
|
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
print "#{SolidObserver::Services::CacheOperations.message(:clear, :confirmation)} (y/N) "
|
|
98
|
+
$stdout.flush
|
|
99
|
+
|
|
100
|
+
if $stdin.gets&.strip&.downcase == "y"
|
|
101
|
+
puts SolidObserver::Services::CacheOperations.clear[:message]
|
|
64
102
|
else
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
puts "Aborted"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
desc "Prune expired SolidCache entries"
|
|
108
|
+
task prune: :environment do
|
|
109
|
+
if !SolidObserver::Services::CacheOperations.available?
|
|
110
|
+
puts SolidObserver::Services::CacheOperations.unavailable_message
|
|
111
|
+
next
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
puts SolidObserver::Services::CacheOperations.prune[:message]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
namespace :cable do
|
|
119
|
+
desc "Trim expired Solid Cable messages"
|
|
120
|
+
task trim: :environment do
|
|
121
|
+
if !SolidObserver::Services::CableOperations.available?
|
|
122
|
+
puts SolidObserver::Services::CableOperations.unavailable_message
|
|
123
|
+
next
|
|
67
124
|
end
|
|
125
|
+
|
|
126
|
+
puts SolidObserver::Services::CableOperations.trim[:message]
|
|
68
127
|
end
|
|
69
128
|
end
|
|
70
129
|
|
|
@@ -75,7 +134,7 @@ namespace :solid_observer do
|
|
|
75
134
|
puts "Running storage cleanup (retention: #{retention.inspect})..."
|
|
76
135
|
|
|
77
136
|
deleted_count = SolidObserver::Services::CleanupStorage.call
|
|
78
|
-
puts "✓ Cleanup complete: #{deleted_count} old
|
|
137
|
+
puts "✓ Cleanup complete: #{deleted_count} old telemetry rows deleted"
|
|
79
138
|
end
|
|
80
139
|
|
|
81
140
|
desc "Purge ALL SolidObserver storage data (use with caution!)"
|
|
@@ -85,10 +144,30 @@ namespace :solid_observer do
|
|
|
85
144
|
|
|
86
145
|
confirmation = $stdin.gets&.strip&.downcase
|
|
87
146
|
if confirmation == "y"
|
|
88
|
-
|
|
147
|
+
data_source_exists = lambda do |model|
|
|
148
|
+
table_name = model.table_name.to_s
|
|
149
|
+
!table_name.empty? && model.connection.data_source_exists?(table_name)
|
|
150
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::StatementInvalid, TypeError
|
|
151
|
+
false
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
telemetry_models = [
|
|
155
|
+
{label: :queue_events, model: SolidObserver::QueueEvent},
|
|
156
|
+
{label: :cache_events, model: SolidObserver::CacheEvent},
|
|
157
|
+
{label: :cache_metrics, model: SolidObserver::CacheMetric}
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
telemetry_counts = telemetry_models.to_h do |entry|
|
|
161
|
+
count = data_source_exists.call(entry[:model]) ? entry[:model].count : 0
|
|
162
|
+
[entry[:label], count]
|
|
163
|
+
end
|
|
89
164
|
snapshot_count = SolidObserver::StorageInfo.count
|
|
90
165
|
|
|
91
|
-
|
|
166
|
+
telemetry_models.each do |entry|
|
|
167
|
+
next unless data_source_exists.call(entry[:model])
|
|
168
|
+
|
|
169
|
+
entry[:model].delete_all
|
|
170
|
+
end
|
|
92
171
|
SolidObserver::StorageInfo.delete_all
|
|
93
172
|
|
|
94
173
|
connection = SolidObserver::QueueEvent.connection
|
|
@@ -97,12 +176,23 @@ namespace :solid_observer do
|
|
|
97
176
|
connection.execute("VACUUM")
|
|
98
177
|
puts "✓ Database vacuumed to reclaim disk space"
|
|
99
178
|
when "postgresql"
|
|
100
|
-
|
|
101
|
-
|
|
179
|
+
[
|
|
180
|
+
SolidObserver::QueueEvent.table_name,
|
|
181
|
+
SolidObserver::StorageInfo.table_name,
|
|
182
|
+
(SolidObserver::CacheEvent.table_name if data_source_exists.call(SolidObserver::CacheEvent)),
|
|
183
|
+
(SolidObserver::CacheMetric.table_name if data_source_exists.call(SolidObserver::CacheMetric))
|
|
184
|
+
].compact.each do |table_name|
|
|
185
|
+
connection.execute("ANALYZE #{table_name}")
|
|
186
|
+
end
|
|
102
187
|
puts "✓ Database tables analyzed"
|
|
103
188
|
end
|
|
104
189
|
|
|
105
|
-
puts
|
|
190
|
+
puts(
|
|
191
|
+
"✓ Purged #{telemetry_counts.fetch(:queue_events)} queue events, " \
|
|
192
|
+
"#{telemetry_counts.fetch(:cache_events)} cache events, " \
|
|
193
|
+
"#{telemetry_counts.fetch(:cache_metrics)} cache metrics, " \
|
|
194
|
+
"and #{snapshot_count} storage snapshots"
|
|
195
|
+
)
|
|
106
196
|
else
|
|
107
197
|
puts "Aborted"
|
|
108
198
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_observer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BartOz
|
|
@@ -54,6 +54,10 @@ files:
|
|
|
54
54
|
- app/controllers/concerns/solid_observer/require_persistence_mode.rb
|
|
55
55
|
- app/controllers/concerns/solid_observer/require_solid_queue.rb
|
|
56
56
|
- app/controllers/solid_observer/application_controller.rb
|
|
57
|
+
- app/controllers/solid_observer/cable_dashboard_controller.rb
|
|
58
|
+
- app/controllers/solid_observer/cable_operations_controller.rb
|
|
59
|
+
- app/controllers/solid_observer/cache_dashboard_controller.rb
|
|
60
|
+
- app/controllers/solid_observer/cache_operations_controller.rb
|
|
57
61
|
- app/controllers/solid_observer/dashboard_controller.rb
|
|
58
62
|
- app/controllers/solid_observer/events_controller.rb
|
|
59
63
|
- app/controllers/solid_observer/jobs_controller.rb
|
|
@@ -61,11 +65,25 @@ files:
|
|
|
61
65
|
- app/helpers/solid_observer/application_helper.rb
|
|
62
66
|
- app/helpers/solid_observer/dashboard_helper.rb
|
|
63
67
|
- app/jobs/solid_observer/cleanup_job.rb
|
|
68
|
+
- app/models/solid_observer/cable_event.rb
|
|
69
|
+
- app/models/solid_observer/cable_metric.rb
|
|
70
|
+
- app/models/solid_observer/cache_event.rb
|
|
71
|
+
- app/models/solid_observer/cache_metric.rb
|
|
64
72
|
- app/models/solid_observer/queue_event.rb
|
|
65
73
|
- app/models/solid_observer/queue_metric.rb
|
|
66
74
|
- app/models/solid_observer/storage_info.rb
|
|
67
75
|
- app/presenters/solid_observer/execution_presenter.rb
|
|
68
76
|
- app/views/layouts/solid_observer/application.html.erb
|
|
77
|
+
- app/views/solid_observer/cable_dashboard/_charts.html.erb
|
|
78
|
+
- app/views/solid_observer/cable_dashboard/_recent_events.html.erb
|
|
79
|
+
- app/views/solid_observer/cable_dashboard/_summary.html.erb
|
|
80
|
+
- app/views/solid_observer/cable_dashboard/index.html.erb
|
|
81
|
+
- app/views/solid_observer/cache_dashboard/_charts.html.erb
|
|
82
|
+
- app/views/solid_observer/cache_dashboard/_recent_events.html.erb
|
|
83
|
+
- app/views/solid_observer/cache_dashboard/_summary.html.erb
|
|
84
|
+
- app/views/solid_observer/cache_dashboard/index.html.erb
|
|
85
|
+
- app/views/solid_observer/cache_operations/_confirm_clear.html.erb
|
|
86
|
+
- app/views/solid_observer/cache_operations/index.html.erb
|
|
69
87
|
- app/views/solid_observer/dashboard/_chart.html.erb
|
|
70
88
|
- app/views/solid_observer/dashboard/_live_state.html.erb
|
|
71
89
|
- app/views/solid_observer/dashboard/_queue_table.html.erb
|
|
@@ -81,19 +99,29 @@ files:
|
|
|
81
99
|
- app/views/solid_observer/shared/_pagination.html.erb
|
|
82
100
|
- app/views/solid_observer/shared/_stat_card.html.erb
|
|
83
101
|
- app/views/solid_observer/storages/show.html.erb
|
|
84
|
-
- bin/console
|
|
85
|
-
- bin/quality_gate
|
|
86
|
-
- bin/setup
|
|
87
102
|
- config/routes.rb
|
|
88
103
|
- db/migrate/20260115000001_create_solid_observer_queue_events.rb
|
|
89
104
|
- db/migrate/20260115000002_create_solid_observer_metrics.rb
|
|
90
105
|
- db/migrate/20260115000003_create_solid_observer_storage_info.rb
|
|
91
106
|
- db/migrate/20260424000001_add_composite_indexes_to_queue_events.rb
|
|
107
|
+
- db/migrate/20260601000001_create_solid_observer_cache_events.rb
|
|
108
|
+
- db/migrate/20260601000002_create_solid_observer_cache_metrics.rb
|
|
109
|
+
- db/migrate/20260602000001_add_component_to_solid_observer_storage_infos.rb
|
|
110
|
+
- db/migrate/20260612000001_add_event_type_recorded_at_index_to_cache_events.rb
|
|
111
|
+
- db/migrate/20260619000001_create_solid_observer_cable_events.rb
|
|
112
|
+
- db/migrate/20260619000002_create_solid_observer_cable_metrics.rb
|
|
92
113
|
- lib/generators/solid_observer/install_generator.rb
|
|
93
114
|
- lib/generators/solid_observer/templates/initializer.rb.tt
|
|
94
115
|
- lib/solid_observer.rb
|
|
95
116
|
- lib/solid_observer/base_event.rb
|
|
96
117
|
- lib/solid_observer/base_metric.rb
|
|
118
|
+
- lib/solid_observer/base_record.rb
|
|
119
|
+
- lib/solid_observer/cable_event_buffer.rb
|
|
120
|
+
- lib/solid_observer/cable_metric_buffer.rb
|
|
121
|
+
- lib/solid_observer/cable_subscriber.rb
|
|
122
|
+
- lib/solid_observer/cache_event_buffer.rb
|
|
123
|
+
- lib/solid_observer/cache_metric_buffer.rb
|
|
124
|
+
- lib/solid_observer/cache_subscriber.rb
|
|
97
125
|
- lib/solid_observer/chart_buffer.rb
|
|
98
126
|
- lib/solid_observer/cli/base.rb
|
|
99
127
|
- lib/solid_observer/cli/jobs.rb
|
|
@@ -102,6 +130,7 @@ files:
|
|
|
102
130
|
- lib/solid_observer/configuration.rb
|
|
103
131
|
- lib/solid_observer/correlation_id_resolver.rb
|
|
104
132
|
- lib/solid_observer/engine.rb
|
|
133
|
+
- lib/solid_observer/event_buffer_core.rb
|
|
105
134
|
- lib/solid_observer/params/events_filter.rb
|
|
106
135
|
- lib/solid_observer/params/jobs_filter.rb
|
|
107
136
|
- lib/solid_observer/queries/events_query.rb
|
|
@@ -109,11 +138,24 @@ files:
|
|
|
109
138
|
- lib/solid_observer/queries/job_executions_query.rb
|
|
110
139
|
- lib/solid_observer/queue_event_buffer.rb
|
|
111
140
|
- lib/solid_observer/queue_stats.rb
|
|
141
|
+
- lib/solid_observer/services/cable_operations.rb
|
|
142
|
+
- lib/solid_observer/services/cable_stats.rb
|
|
143
|
+
- lib/solid_observer/services/cache_operations.rb
|
|
144
|
+
- lib/solid_observer/services/cache_stats.rb
|
|
112
145
|
- lib/solid_observer/services/cleanup_storage.rb
|
|
113
146
|
- lib/solid_observer/services/database_size.rb
|
|
147
|
+
- lib/solid_observer/services/flush_cable_event_buffer.rb
|
|
148
|
+
- lib/solid_observer/services/flush_cable_metrics.rb
|
|
149
|
+
- lib/solid_observer/services/flush_cache_event_buffer.rb
|
|
150
|
+
- lib/solid_observer/services/flush_cache_metrics.rb
|
|
114
151
|
- lib/solid_observer/services/flush_event_buffer.rb
|
|
115
152
|
- lib/solid_observer/services/install_migrations.rb
|
|
153
|
+
- lib/solid_observer/services/record_cable_event.rb
|
|
154
|
+
- lib/solid_observer/services/record_cable_metric.rb
|
|
155
|
+
- lib/solid_observer/services/record_cache_event.rb
|
|
156
|
+
- lib/solid_observer/services/record_cache_metric.rb
|
|
116
157
|
- lib/solid_observer/services/record_event.rb
|
|
158
|
+
- lib/solid_observer/services/storage_info_snapshot.rb
|
|
117
159
|
- lib/solid_observer/services/ui_auth_check.rb
|
|
118
160
|
- lib/solid_observer/subscriber.rb
|
|
119
161
|
- lib/solid_observer/version.rb
|
|
@@ -141,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
183
|
- !ruby/object:Gem::Version
|
|
142
184
|
version: '0'
|
|
143
185
|
requirements: []
|
|
144
|
-
rubygems_version: 4.0.
|
|
186
|
+
rubygems_version: 4.0.6
|
|
145
187
|
specification_version: 4
|
|
146
188
|
summary: Observability for the Rails 8's Solid Stack
|
|
147
189
|
test_files: []
|
data/bin/console
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "bundler/setup"
|
|
5
|
-
require "solid_observer"
|
|
6
|
-
|
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
-
|
|
10
|
-
require "irb"
|
|
11
|
-
IRB.start(__FILE__)
|