solid_observer 0.4.0 → 0.6.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 +30 -0
- data/README.md +100 -25
- 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 +33 -40
- data/app/controllers/solid_observer/dashboard_controller.rb +21 -13
- data/app/controllers/solid_observer/traces_controller.rb +17 -0
- data/app/helpers/solid_observer/application_helper.rb +145 -3
- 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_metric.rb +1 -2
- data/app/models/solid_observer/storage_info.rb +1 -1
- data/app/views/layouts/solid_observer/application.html.erb +35 -9
- data/app/views/solid_observer/cable_dashboard/_charts.html.erb +31 -0
- data/app/views/solid_observer/cable_dashboard/_recent_events.html.erb +39 -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/_recent_events.html.erb +6 -1
- data/app/views/solid_observer/dashboard/_component_cards.html.erb +18 -0
- data/app/views/solid_observer/dashboard/_health.html.erb +6 -0
- data/app/views/solid_observer/dashboard/_home.html.erb +10 -0
- data/app/views/solid_observer/dashboard/_queue.html.erb +138 -0
- data/app/views/solid_observer/dashboard/_queue_table.html.erb +1 -0
- data/app/views/solid_observer/dashboard/_unified_feed.html.erb +38 -0
- data/app/views/solid_observer/dashboard/index.html.erb +3 -139
- data/app/views/solid_observer/events/index.html.erb +7 -1
- data/app/views/solid_observer/jobs/index.html.erb +1 -0
- data/app/views/solid_observer/storages/show.html.erb +29 -3
- data/app/views/solid_observer/traces/show.html.erb +80 -0
- data/config/routes.rb +3 -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/db/migrate/20260630000001_add_correlation_id_to_cache_events.rb +8 -0
- data/db/migrate/20260630000002_add_correlation_id_to_cable_events.rb +8 -0
- data/lib/generators/solid_observer/install_generator.rb +8 -1
- data/lib/generators/solid_observer/templates/initializer.rb.tt +24 -5
- 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 +11 -36
- data/lib/solid_observer/cache_metric_buffer.rb +229 -0
- data/lib/solid_observer/chart_buffer.rb +84 -27
- data/lib/solid_observer/cli/health.rb +49 -0
- data/lib/solid_observer/cli/trace.rb +106 -0
- data/lib/solid_observer/configuration.rb +47 -7
- data/lib/solid_observer/correlated.rb +23 -0
- data/lib/solid_observer/engine.rb +46 -28
- data/lib/solid_observer/event_buffer_core.rb +218 -0
- data/lib/solid_observer/queries/trace_query.rb +151 -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_stats.rb +35 -18
- data/lib/solid_observer/services/cleanup_storage.rb +82 -47
- 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_metrics.rb +56 -0
- data/lib/solid_observer/services/health_score.rb +85 -0
- data/lib/solid_observer/services/record_cable_event.rb +115 -0
- data/lib/solid_observer/services/record_cable_metric.rb +73 -0
- data/lib/solid_observer/services/record_cache_event.rb +24 -0
- data/lib/solid_observer/services/record_cache_metric.rb +13 -21
- data/lib/solid_observer/services/storage_info_snapshot.rb +103 -15
- data/lib/solid_observer/services/unified_feed.rb +101 -0
- data/lib/solid_observer/version.rb +1 -1
- data/lib/solid_observer.rb +42 -11
- data/lib/tasks/solid_observer.rake +97 -21
- metadata +41 -6
- data/app/assets/stylesheets/solid_observer/application.css +0 -18
- data/bin/console +0 -11
- data/bin/quality_gate +0 -95
- data/bin/setup +0 -8
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidObserver
|
|
4
|
+
module Services
|
|
5
|
+
class FlushCableEventBuffer
|
|
6
|
+
BATCH_SIZE = 100
|
|
7
|
+
|
|
8
|
+
def self.call(events)
|
|
9
|
+
new(events).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(events)
|
|
13
|
+
@events = events
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
return 0 if @events.empty?
|
|
18
|
+
|
|
19
|
+
insert_all_events
|
|
20
|
+
@events.size
|
|
21
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => error
|
|
22
|
+
fallback_insert_count(error)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def insert_all_events
|
|
28
|
+
CableEvent.transaction { CableEvent.insert_all!(@events) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fallback_insert_count(error)
|
|
32
|
+
Rails.logger&.error("[SolidObserver] Cable bulk insert failed, retrying in batches: #{error.message}") if defined?(Rails)
|
|
33
|
+
@events.each_slice(BATCH_SIZE).sum { |batch| insert_batch(batch) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def insert_batch(batch)
|
|
37
|
+
size = batch.size
|
|
38
|
+
CableEvent.insert_all(batch, returning: false)
|
|
39
|
+
size
|
|
40
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => error
|
|
41
|
+
handle_batch_insert_error(size, error)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def log_batch_warning(size, error)
|
|
45
|
+
Rails.logger&.warn("[SolidObserver] Failed to insert cable batch of #{size} events: #{error.message}") if defined?(Rails)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handle_batch_insert_error(size, error)
|
|
49
|
+
log_batch_warning(size, error)
|
|
50
|
+
0
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidObserver
|
|
4
|
+
module Services
|
|
5
|
+
class FlushCableMetrics
|
|
6
|
+
def self.call(metrics)
|
|
7
|
+
new(metrics).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(metrics)
|
|
11
|
+
@metrics = metrics
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
return 0 if @metrics.empty?
|
|
16
|
+
|
|
17
|
+
flush_metrics
|
|
18
|
+
@metrics.size
|
|
19
|
+
rescue ActiveRecord::RecordNotUnique
|
|
20
|
+
retry
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def flush_metrics
|
|
26
|
+
SolidObserver::CableMetric.transaction do
|
|
27
|
+
@metrics.each { |metric_data| increment_metric(metric_data) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def increment_metric(metric_data)
|
|
32
|
+
period_start = metric_data.fetch(:period_start)
|
|
33
|
+
metric = SolidObserver::CableMetric.find_or_create_by!(period_start: period_start)
|
|
34
|
+
|
|
35
|
+
SolidObserver::CableMetric.where(id: metric.id).update_counters(update_values(metric_data))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def update_values(metric_data)
|
|
39
|
+
{
|
|
40
|
+
broadcasts_count: increment_expression(:broadcasts_count, metric_data),
|
|
41
|
+
transmissions_count: increment_expression(:transmissions_count, metric_data),
|
|
42
|
+
confirmations_count: increment_expression(:confirmations_count, metric_data),
|
|
43
|
+
rejections_count: increment_expression(:rejections_count, metric_data),
|
|
44
|
+
perform_actions_count: increment_expression(:perform_actions_count, metric_data),
|
|
45
|
+
errors_count: increment_expression(:errors_count, metric_data)
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def increment_expression(column, metric_data)
|
|
50
|
+
metric_data.fetch(column, 0)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidObserver
|
|
4
|
+
module Services
|
|
5
|
+
class FlushCacheMetrics
|
|
6
|
+
def self.call(metrics)
|
|
7
|
+
new(metrics).call
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(metrics)
|
|
11
|
+
@metrics = metrics
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
return 0 if @metrics.empty?
|
|
16
|
+
|
|
17
|
+
flush_metrics
|
|
18
|
+
@metrics.size
|
|
19
|
+
rescue ActiveRecord::RecordNotUnique
|
|
20
|
+
retry
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def flush_metrics
|
|
26
|
+
SolidObserver::CacheMetric.transaction do
|
|
27
|
+
@metrics.each { |metric_data| increment_metric(metric_data) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def increment_metric(metric_data)
|
|
32
|
+
event_type, period_start = metric_data.values_at(:event_type, :period_start)
|
|
33
|
+
metric = SolidObserver::CacheMetric.find_or_create_by!(
|
|
34
|
+
event_type: event_type,
|
|
35
|
+
period_start: period_start
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
SolidObserver::CacheMetric.where(id: metric.id).update_counters(update_values(metric_data))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update_values(metric_data)
|
|
42
|
+
{
|
|
43
|
+
operations_count: increment_expression(:operations_count, metric_data),
|
|
44
|
+
hits_count: increment_expression(:hits_count, metric_data),
|
|
45
|
+
misses_count: increment_expression(:misses_count, metric_data),
|
|
46
|
+
errors_count: increment_expression(:errors_count, metric_data),
|
|
47
|
+
duration_total: increment_expression(:duration_total, metric_data)
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def increment_expression(column, metric_data)
|
|
52
|
+
metric_data.fetch(column, 0)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/numeric/time"
|
|
4
|
+
|
|
5
|
+
module SolidObserver
|
|
6
|
+
module Services
|
|
7
|
+
class HealthScore
|
|
8
|
+
STATUS_RANK = {stable: 0, degraded: 1, critical: 2}.freeze
|
|
9
|
+
UNAVAILABLE = {status: :degraded, available: false}.freeze
|
|
10
|
+
WINDOW = 15.minutes
|
|
11
|
+
|
|
12
|
+
def self.call
|
|
13
|
+
new.call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
components = collect_components
|
|
18
|
+
{
|
|
19
|
+
overall: overall_status(components.values.map { |entry| entry[:status] }),
|
|
20
|
+
components: components
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# :reek:FeatureEnvy
|
|
27
|
+
# :reek:TooManyStatements
|
|
28
|
+
def collect_components
|
|
29
|
+
config = SolidObserver.config
|
|
30
|
+
components = {}
|
|
31
|
+
|
|
32
|
+
components[:queue] = queue_component if config.solid_queue_enabled?
|
|
33
|
+
|
|
34
|
+
unless config.realtime_mode?
|
|
35
|
+
components[:cache] = stats_component(:cache, CacheStats) if config.solid_cache_enabled?
|
|
36
|
+
components[:cable] = stats_component(:cable, CableStats) if config.solid_cable_enabled?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
components
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def queue_component
|
|
43
|
+
snapshot = SolidObserver::QueueStats.snapshot
|
|
44
|
+
return UNAVAILABLE.dup unless snapshot[:available]
|
|
45
|
+
|
|
46
|
+
{status: queue_stability(snapshot), available: true}
|
|
47
|
+
rescue => error
|
|
48
|
+
degrade(:queue, error)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def queue_stability(snapshot)
|
|
52
|
+
return :critical if snapshot[:failed_last_hour].to_i.positive?
|
|
53
|
+
return :degraded if snapshot[:failed_last_24h].to_i.positive?
|
|
54
|
+
|
|
55
|
+
:stable
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def stats_component(name, stats_class)
|
|
59
|
+
stability = stats_class.call(window: WINDOW)[:stability]
|
|
60
|
+
return UNAVAILABLE.dup unless stability&.[](:available)
|
|
61
|
+
|
|
62
|
+
{status: stability[:state] || :degraded, available: true}
|
|
63
|
+
rescue => error
|
|
64
|
+
degrade(name, error)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def overall_status(statuses)
|
|
68
|
+
return :stable if statuses.empty?
|
|
69
|
+
|
|
70
|
+
statuses.max_by { |status| STATUS_RANK.fetch(status, 1) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def degrade(name, error)
|
|
74
|
+
log_component_error(name, error)
|
|
75
|
+
UNAVAILABLE.dup
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def log_component_error(name, error)
|
|
79
|
+
return unless defined?(Rails)
|
|
80
|
+
|
|
81
|
+
Rails.logger&.warn("HealthScore #{name} error: #{error.message}")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module SolidObserver
|
|
6
|
+
module Services
|
|
7
|
+
class RecordCableEvent
|
|
8
|
+
def self.call(event:, buffer:)
|
|
9
|
+
new(event, buffer).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(event, buffer)
|
|
13
|
+
@event = event
|
|
14
|
+
@buffer = buffer
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
record_metric_and_event
|
|
19
|
+
rescue => error
|
|
20
|
+
raise error if error.is_a?(NameError)
|
|
21
|
+
Rails.logger&.warn("[SolidObserver] Cable event recording failed: #{error.message}") if defined?(Rails)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def record_metric_and_event
|
|
27
|
+
SolidObserver::Services::RecordCableMetric.call(event: @event)
|
|
28
|
+
return unless should_store_event?
|
|
29
|
+
|
|
30
|
+
@buffer.push(build_event_data)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def should_store_event?
|
|
34
|
+
case @event.name
|
|
35
|
+
when "broadcast.action_cable"
|
|
36
|
+
sampled? || errored?
|
|
37
|
+
when "transmit_subscription_rejection.action_cable"
|
|
38
|
+
true
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sampled?
|
|
45
|
+
rand <= SolidObserver.config.cable_sampling_rate
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def errored?
|
|
49
|
+
!exception_data.compact.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_event_data
|
|
53
|
+
{
|
|
54
|
+
event_type: @event.name.delete_suffix(".action_cable"),
|
|
55
|
+
correlation_id: CorrelationIdResolver.resolve(@event),
|
|
56
|
+
channel_class: channel_class,
|
|
57
|
+
broadcasting_digest: broadcasting_digest,
|
|
58
|
+
duration: duration_in_seconds,
|
|
59
|
+
error_class: exception_data[:error_class],
|
|
60
|
+
error_message: exception_data[:error_message],
|
|
61
|
+
metadata: metadata.to_json,
|
|
62
|
+
recorded_at: Time.current
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def broadcasting_digest
|
|
67
|
+
return nil unless @event.name == "broadcast.action_cable"
|
|
68
|
+
|
|
69
|
+
Digest::SHA256.hexdigest(payload[:broadcasting].to_s)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def channel_class
|
|
73
|
+
class_name = payload[:channel_class]
|
|
74
|
+
return class_name if class_name
|
|
75
|
+
|
|
76
|
+
channel = payload[:channel]
|
|
77
|
+
return nil unless channel
|
|
78
|
+
|
|
79
|
+
channel.class.name || channel.to_s
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def duration_in_seconds
|
|
83
|
+
@event.duration&./(1000.0)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def metadata
|
|
87
|
+
{
|
|
88
|
+
action: payload[:action]&.to_s,
|
|
89
|
+
via: payload[:via]&.to_s,
|
|
90
|
+
data_size: payload[:data]&.to_s&.bytesize,
|
|
91
|
+
message_size: payload[:message]&.to_s&.bytesize
|
|
92
|
+
}.compact
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def exception_data
|
|
96
|
+
@exception_data ||= begin
|
|
97
|
+
exception_obj = payload[:exception_object]
|
|
98
|
+
exception = payload[:exception]
|
|
99
|
+
|
|
100
|
+
if exception_obj
|
|
101
|
+
{error_class: exception_obj.class.name, error_message: exception_obj.message}
|
|
102
|
+
elsif exception.is_a?(Array)
|
|
103
|
+
{error_class: exception.first, error_message: exception.last}
|
|
104
|
+
else
|
|
105
|
+
{}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def payload
|
|
111
|
+
@event.payload || {}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../cable_metric_buffer"
|
|
4
|
+
|
|
5
|
+
module SolidObserver
|
|
6
|
+
module Services
|
|
7
|
+
class RecordCableMetric
|
|
8
|
+
COUNTERS = %i[
|
|
9
|
+
broadcasts_count
|
|
10
|
+
transmissions_count
|
|
11
|
+
confirmations_count
|
|
12
|
+
rejections_count
|
|
13
|
+
perform_actions_count
|
|
14
|
+
errors_count
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
EVENT_COUNTER_MAP = {
|
|
18
|
+
"broadcast.action_cable" => :broadcasts_count,
|
|
19
|
+
"transmit.action_cable" => :transmissions_count,
|
|
20
|
+
"transmit_subscription_confirmation.action_cable" => :confirmations_count,
|
|
21
|
+
"transmit_subscription_rejection.action_cable" => :rejections_count,
|
|
22
|
+
"perform_action.action_cable" => :perform_actions_count
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def self.call(event:, buffer: SolidObserver::CableMetricBuffer.instance)
|
|
26
|
+
new(event, buffer).call
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(event, buffer)
|
|
30
|
+
@event = event
|
|
31
|
+
@buffer = buffer
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def call
|
|
35
|
+
@buffer.increment(metric_data)
|
|
36
|
+
rescue => error
|
|
37
|
+
Rails.logger&.warn("[SolidObserver] Cable metric recording failed: #{error.message}") if defined?(Rails)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def metric_data
|
|
43
|
+
COUNTERS.each_with_object({period_start: period_start}) do |counter, data|
|
|
44
|
+
data[counter] = (counter == target_counter) ? 1 : 0
|
|
45
|
+
end.merge(errors_count: error_increment)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def target_counter
|
|
49
|
+
EVENT_COUNTER_MAP.fetch(event_name, :broadcasts_count)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def period_start
|
|
53
|
+
Time.current.beginning_of_minute
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def event_name
|
|
57
|
+
@event.name
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def error_increment
|
|
61
|
+
exception_present? ? 1 : 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def exception_present?
|
|
65
|
+
payload[:exception_object].present? || payload[:exception].is_a?(Array)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def payload
|
|
69
|
+
@event.payload || {}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -5,6 +5,8 @@ require "digest"
|
|
|
5
5
|
module SolidObserver
|
|
6
6
|
module Services
|
|
7
7
|
class RecordCacheEvent
|
|
8
|
+
INTERNAL_CACHE_KEY_PREFIX = "solid_observer/"
|
|
9
|
+
|
|
8
10
|
def self.call(event:, buffer:)
|
|
9
11
|
new(event, buffer).call
|
|
10
12
|
end
|
|
@@ -15,6 +17,8 @@ module SolidObserver
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def call
|
|
20
|
+
return if internal_cache_event?
|
|
21
|
+
|
|
18
22
|
record_metric_and_event
|
|
19
23
|
rescue => error
|
|
20
24
|
raise error if error.is_a?(NameError)
|
|
@@ -51,6 +55,7 @@ module SolidObserver
|
|
|
51
55
|
def build_event_data
|
|
52
56
|
{
|
|
53
57
|
event_type: cache_operation,
|
|
58
|
+
correlation_id: CorrelationIdResolver.resolve(@event),
|
|
54
59
|
key_digest: key_digest,
|
|
55
60
|
hit: hit_value,
|
|
56
61
|
duration: duration_in_seconds,
|
|
@@ -137,6 +142,25 @@ module SolidObserver
|
|
|
137
142
|
def payload
|
|
138
143
|
@event.payload || {}
|
|
139
144
|
end
|
|
145
|
+
|
|
146
|
+
def internal_cache_event?
|
|
147
|
+
internal_cache_key?(payload[:key])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def internal_cache_key?(key)
|
|
151
|
+
case key
|
|
152
|
+
when Array
|
|
153
|
+
nested_internal_cache_key?(key)
|
|
154
|
+
when Hash
|
|
155
|
+
nested_internal_cache_key?(key.flatten(1))
|
|
156
|
+
else
|
|
157
|
+
key.to_s.start_with?(INTERNAL_CACHE_KEY_PREFIX)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def nested_internal_cache_key?(entries)
|
|
162
|
+
entries.any? { |entry| internal_cache_key?(entry) }
|
|
163
|
+
end
|
|
140
164
|
end
|
|
141
165
|
end
|
|
142
166
|
end
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../cache_metric_buffer"
|
|
4
|
+
|
|
3
5
|
module SolidObserver
|
|
4
6
|
module Services
|
|
5
7
|
class RecordCacheMetric
|
|
6
|
-
def self.call(event:)
|
|
7
|
-
new(event).call
|
|
8
|
+
def self.call(event:, buffer: SolidObserver::CacheMetricBuffer.instance)
|
|
9
|
+
new(event, buffer).call
|
|
8
10
|
end
|
|
9
11
|
|
|
10
|
-
def initialize(event)
|
|
12
|
+
def initialize(event, buffer)
|
|
11
13
|
@event = event
|
|
14
|
+
@buffer = buffer
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def call
|
|
15
|
-
|
|
18
|
+
@buffer.increment(
|
|
16
19
|
event_type: event_type,
|
|
17
|
-
period_start: period_start
|
|
20
|
+
period_start: period_start,
|
|
21
|
+
operations_count: 1,
|
|
22
|
+
hits_count: hit_increment,
|
|
23
|
+
misses_count: miss_increment,
|
|
24
|
+
errors_count: error_increment,
|
|
25
|
+
duration_total: duration_in_seconds
|
|
18
26
|
)
|
|
19
|
-
|
|
20
|
-
SolidObserver::CacheMetric
|
|
21
|
-
.where(id: metric.id)
|
|
22
|
-
.update_all(update_values)
|
|
23
|
-
rescue ActiveRecord::RecordNotUnique
|
|
24
|
-
retry
|
|
25
27
|
rescue => error
|
|
26
28
|
Rails.logger&.warn("[SolidObserver] Cache metric recording failed: #{error.message}") if defined?(Rails)
|
|
27
29
|
end
|
|
@@ -36,16 +38,6 @@ module SolidObserver
|
|
|
36
38
|
@event.name.delete_suffix(".active_support")
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def update_values
|
|
40
|
-
{
|
|
41
|
-
operations_count: Arel.sql("operations_count + 1"),
|
|
42
|
-
hits_count: Arel.sql("hits_count + #{hit_increment}"),
|
|
43
|
-
misses_count: Arel.sql("misses_count + #{miss_increment}"),
|
|
44
|
-
errors_count: Arel.sql("errors_count + #{error_increment}"),
|
|
45
|
-
duration_total: Arel.sql("duration_total + #{duration_in_seconds}")
|
|
46
|
-
}
|
|
47
|
-
end
|
|
48
|
-
|
|
49
41
|
def hit_increment
|
|
50
42
|
(payload[:hit] == true) ? 1 : 0
|
|
51
43
|
end
|