solid_apm 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc8464230e852711199328548a375cc742873a408f04b34fd793e305d854420a
4
- data.tar.gz: 27a42427c5fb310b459bcf673c9fedd4dc1a0d0852a15fb4fa367bc6fed475a8
3
+ metadata.gz: e3d5be7324da26b5f4720bc0ba9e61717be304286b9ecad84e09adf503095699
4
+ data.tar.gz: 8969d758c906bb7de44b4024e1f7353bc63f1f4dda0f2ee5631df1e2fa436e2e
5
5
  SHA512:
6
- metadata.gz: c249809b73d29facaccf681ba95bf60fce23debccc69850f8bb309d826d5b13e7ec093d2acb5346518e0e1c8b5a479743735fa7071a69b63e5a3672f3c5edb00
7
- data.tar.gz: '093e4a4e39e436b26307a67559517ac2e1cc3e3375aa7afeb980db674301e0571b74e74ad68ca8895667557465461e790b1158d0ab8c48f16f53ad301cb37eb8'
6
+ metadata.gz: 1ed2237e30cabd350515555a5e6a66006777835d4e07adcc2dc2758f0fe8e474e931e3da204f44309671923ba1eb1fbcbc32203eaf459a6cebffc2eb3030f3fa
7
+ data.tar.gz: fb86bb4e14bf08207255ca5e0121b6e50a5ba43e3a4d394d777362df614d47d25e0d54508a66660c01c7ecccf3c53cc00b395981bee6a32378a5903ca64dd268
data/README.md CHANGED
@@ -52,6 +52,113 @@ class ApplicationController
52
52
  end
53
53
  ```
54
54
 
55
+ ## Configuration
56
+
57
+ SolidAPM can be configured using the following options in your `config/initializers/solid_apm.rb` file:
58
+
59
+ ### Database Connection
60
+
61
+ Configure the database connection for SolidAPM:
62
+
63
+ ```ruby
64
+ SolidApm.connects_to = { database: { writing: :solid_apm } }
65
+ ```
66
+
67
+ ### ActiveRecord Logger Silencing
68
+
69
+ Control whether ActiveRecord logger is silenced during SolidAPM operations (default: `true`):
70
+
71
+ ```ruby
72
+ # Disable ActiveRecord logger silencing to see SQL queries in logs
73
+ SolidApm.silence_active_record_logger = false
74
+ ```
75
+
76
+ ### Transaction Sampling
77
+
78
+ Control the sampling rate for transactions using a "1 out of N" approach (default: `1`):
79
+
80
+ ```ruby
81
+ # Sample every transaction (default behavior)
82
+ SolidApm.transaction_sampling = 1
83
+
84
+ # Sample 1 out of every 2 transactions (50% sampling)
85
+ SolidApm.transaction_sampling = 2
86
+
87
+ # Sample 1 out of every 5 transactions (20% sampling)
88
+ SolidApm.transaction_sampling = 5
89
+
90
+ # Sample 1 out of every 10 transactions (10% sampling)
91
+ SolidApm.transaction_sampling = 10
92
+ ```
93
+
94
+ The sampling is done per-thread using a round-robin counter, ensuring even distribution across requests.
95
+ This is useful for high-traffic applications where you want to reduce the volume of
96
+ APM data while still maintaining representative performance insights.
97
+
98
+ ### Transaction Name Filtering
99
+
100
+ Filter specific transactions by name using exact string matches or regular expressions:
101
+
102
+ ```ruby
103
+ # Filter specific transactions by exact name
104
+ SolidApm.transaction_filters += ['HomeController#index', /^Rails::HealthController/]
105
+ ```
106
+
107
+ ## Data Cleanup
108
+
109
+ SolidAPM provides a rake task to clean up old transaction data to manage database size over time.
110
+
111
+ ### Manual Cleanup
112
+
113
+ Clean up transactions older than 1 month (default):
114
+
115
+ ```shell
116
+ bin/rails solid_apm:cleanup
117
+ ```
118
+
119
+ Clean up transactions with custom time periods:
120
+
121
+ ```shell
122
+ # Delete transactions older than 1 week
123
+ bin/rails solid_apm:cleanup[1.week.ago]
124
+ ```
125
+
126
+ ### Automated Cleanup with ActiveJob
127
+
128
+ For production applications, it's recommended to set up automated cleanup.
129
+
130
+ Example with SolidQueue. Configure recurring cleanup in your `config/recurring.yml`:
131
+
132
+ ```yaml
133
+ solid_apm_cleanup_weekly:
134
+ class: SolidApm::CleanupJob
135
+ cron: "0 3 * * *" # Every day at 3 AM
136
+ args: ["1.week.ago"]
137
+ ```
138
+
139
+ ## How it works
140
+
141
+ SolidAPM stores information in the form of transactions, representing incoming HTTP requests which
142
+ listen to a variety of spans (events) from `ActiveSupport::Instrument`. Each span
143
+ saves backtrace information to easily find the source of issues.
144
+
145
+ ### Request transaction
146
+
147
+ It is based on [ActionDispatch](https://guides.rubyonrails.org/active_support_instrumentation.html#action-dispatch)
148
+ events to start and end a transaction.
149
+
150
+ A Rack middleware uses [`rack.after_reply`](https://github.blog/engineering/architecture-optimization/performance-at-github-deferring-stats-with-rack-after_reply/)
151
+ to bulk insert transactions and spans after delivering the response, so tracking your application
152
+ doesn't add delay to the client.
153
+
154
+ ### Spans saved
155
+
156
+ * Request
157
+ * Rendering
158
+ * SQL requests and transactions
159
+ * Rails cache
160
+ * Net/HTTP
161
+
55
162
  ## MCP Server
56
163
 
57
164
  SolidAPM offers an optional MCP server to allow an AI agent to interact with SolidAPM
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidApm
4
+ class CleanupJob < ApplicationJob
5
+ def perform(older_than = '1.month.ago')
6
+ result = CleanupService.new(older_than: older_than).call
7
+
8
+ Rails.logger.info "SolidApm::CleanupJob completed: deleted #{result[:deleted_count]} transactions older than #{result[:cutoff_time]} (#{result[:older_than]})"
9
+
10
+ result
11
+ end
12
+ end
13
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module SolidApm
3
4
  module SpanSubscriber
4
5
  class Base
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidApm
4
+ class CleanupService
5
+ # Regex to match safe time expressions like "1.week.ago", "2.months.ago", etc.
6
+ DURATION_PATTERN = /\A(\d+)\.(second|minute|hour|day|week|month|year)s?\.ago\z/.freeze
7
+ def initialize(older_than: '1.month.ago')
8
+ @older_than = older_than
9
+ end
10
+
11
+ def call
12
+ cutoff_time = parse_time_expression(@older_than)
13
+ deleted_count = Transaction.where(timestamp: ...cutoff_time).destroy_all.size
14
+
15
+ {
16
+ cutoff_time: cutoff_time,
17
+ deleted_count: deleted_count,
18
+ older_than: @older_than
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def parse_time_expression(expression)
25
+ match = expression.match(DURATION_PATTERN)
26
+ raise ArgumentError, 'Invalid time expression format' unless match
27
+
28
+ number = match[1].to_i
29
+ unit = match[2]
30
+
31
+ case unit
32
+ when 'second'
33
+ number.seconds.ago
34
+ when 'minute'
35
+ number.minutes.ago
36
+ when 'hour'
37
+ number.hours.ago
38
+ when 'day'
39
+ number.days.ago
40
+ when 'week'
41
+ number.weeks.ago
42
+ when 'month'
43
+ number.months.ago
44
+ when 'year'
45
+ number.years.ago
46
+ else
47
+ raise ArgumentError, "Unsupported time unit: #{unit}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -23,7 +23,11 @@ module SolidApm
23
23
  def self.call
24
24
  transaction = SpanSubscriber::Base.transaction
25
25
  SpanSubscriber::Base.transaction = nil
26
- if transaction.nil? || transaction.name.start_with?('SolidApm::') || transaction.name.start_with?('ActionDispatch::Request::PASS_NOT_FOUND')
26
+
27
+ if transaction.nil? ||
28
+ transaction_filtered?(transaction.name) ||
29
+ !Sampler.should_sample?
30
+
27
31
  SpanSubscriber::Base.spans = nil
28
32
  return
29
33
  end
@@ -41,8 +45,21 @@ module SolidApm
41
45
  SpanSubscriber::Base.spans = nil
42
46
  end
43
47
 
48
+ def self.transaction_filtered?(transaction_name)
49
+ SolidApm.transaction_filters.any? do |filter|
50
+ case filter
51
+ when String
52
+ transaction_name == filter
53
+ when Regexp
54
+ filter.match?(transaction_name)
55
+ else
56
+ false
57
+ end
58
+ end
59
+ end
60
+
44
61
  def self.with_silence_logger
45
- if ActiveRecord::Base.logger
62
+ if SolidApm.silence_active_record_logger && ActiveRecord::Base.logger
46
63
  ActiveRecord::Base.logger.silence { yield }
47
64
  else
48
65
  yield
@@ -0,0 +1,12 @@
1
+ module SolidApm
2
+ class Sampler
3
+ def self.should_sample?
4
+ return true if SolidApm.transaction_sampling <= 1
5
+
6
+ thread_counter = Thread.current[:solid_apm_counter] ||= 0
7
+ Thread.current[:solid_apm_counter] = (thread_counter + 1) % SolidApm.transaction_sampling
8
+
9
+ Thread.current[:solid_apm_counter] == 0
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module SolidApm
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/solid_apm.rb CHANGED
@@ -3,12 +3,23 @@ require 'groupdate'
3
3
  require 'active_median'
4
4
  require 'apexcharts'
5
5
 
6
- require "solid_apm/version"
7
- require "solid_apm/engine"
6
+ require 'solid_apm/version'
7
+ require 'solid_apm/engine'
8
+ require 'solid_apm/sampler'
9
+ require 'solid_apm/cleanup_service'
8
10
 
9
11
  module SolidApm
10
12
  mattr_accessor :connects_to
11
13
  mattr_accessor :mcp_server_config, default: {}
14
+ mattr_accessor :silence_active_record_logger, default: true
15
+ mattr_accessor :transaction_sampling, default: 1
16
+ mattr_accessor(
17
+ :transaction_filters, default: [
18
+ /^SolidApm::/,
19
+ /^ActionDispatch::Request::PASS_NOT_FOUND/,
20
+ 'Rails::HealthController#show'
21
+ ]
22
+ )
12
23
 
13
24
  def self.set_context(context)
14
25
  SpanSubscriber::Base.context = context
@@ -1,4 +1,18 @@
1
- # desc "Explaining what the task does"
2
- # task :solid_apm do
3
- # # Task goes here
4
- # end
1
+ namespace :solid_apm do
2
+ desc 'Delete old transactions (default: older than 1 month). Usage: rake solid_apm:cleanup[1.week.ago]'
3
+ task :cleanup, [:older_than] => :environment do |_task, args|
4
+ older_than = args[:older_than] || '1.month.ago'
5
+
6
+ begin
7
+ result = SolidApm::CleanupService.new(older_than: older_than).call
8
+
9
+ puts "Deleting transactions older than #{result[:cutoff_time]}..."
10
+ puts "Deleted #{result[:deleted_count]} transactions"
11
+ rescue StandardError => e
12
+ puts "Error: #{e.message}"
13
+ puts "Please provide a valid time expression like '1.week.ago', '2.months.ago', etc."
14
+ puts 'Supported formats: [number].[unit].ago where unit is: second(s), minute(s), hour(s), day(s), week(s), month(s), year(s)'
15
+ exit 1
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Francis Bastien
@@ -125,6 +125,7 @@ files:
125
125
  - app/controllers/solid_apm/transactions_controller.rb
126
126
  - app/helpers/solid_apm/application_helper.rb
127
127
  - app/jobs/solid_apm/application_job.rb
128
+ - app/jobs/solid_apm/cleanup_job.rb
128
129
  - app/models/solid_apm/application_record.rb
129
130
  - app/models/solid_apm/span.rb
130
131
  - app/models/solid_apm/span_subscriber/action_dispatch.rb
@@ -145,10 +146,12 @@ files:
145
146
  - db/migrate/20240608015633_create_solid_apm_transactions.rb
146
147
  - db/migrate/20240608021940_create_solid_apm_spans.rb
147
148
  - lib/solid_apm.rb
149
+ - lib/solid_apm/cleanup_service.rb
148
150
  - lib/solid_apm/engine.rb
149
151
  - lib/solid_apm/mcp/impactful_transactions_resource.rb
150
152
  - lib/solid_apm/mcp/spans_for_transaction_tool.rb
151
153
  - lib/solid_apm/middleware.rb
154
+ - lib/solid_apm/sampler.rb
152
155
  - lib/solid_apm/version.rb
153
156
  - lib/tasks/solid_apm_tasks.rake
154
157
  homepage: https://github.com/Bhacaz/solid_apm
@@ -172,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
175
  - !ruby/object:Gem::Version
173
176
  version: '0'
174
177
  requirements: []
175
- rubygems_version: 3.6.7
178
+ rubygems_version: 3.6.9
176
179
  specification_version: 4
177
180
  summary: SolidApm is a DB base engine for Application Performance Monitoring.
178
181
  test_files: []