solid_apm 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6159733df2a423e8080f5d8e118ead35dc63e70cd7ce41c0dd1b82c33922b07
4
- data.tar.gz: 99752422f96489651453e355e358e8c60cf9c02ae52de2793d475a03599ce796
3
+ metadata.gz: f8623eafef426a98627eef8810538b4ad1100a125eadcdb99bf2db0df21cadc1
4
+ data.tar.gz: 386f31259d69c1ae17a182c1b6faf6368b8802c2bccf2985fa9e7853f9fc3ddb
5
5
  SHA512:
6
- metadata.gz: 2c41be36b7d999e112c283247062740eca1bbc27b150329e3300385ec40f4e983efdf2f47ac25f4c6e176b6f605a19894368d85dda8e69e826c4a02afafb745a
7
- data.tar.gz: 9ee48dc1a610c5e2726908bbb78ed526ef1d62641d894af558ea932022c77308f11ef6ec392565f8ad96e3815ecaadf1d36acc4a43d245569c372ae198c59aa2
6
+ metadata.gz: 2cc471473ae3e41df512e12b6183c0410aaae6325f8a21db2f78e6f84caf22824b503e411eaa8f4508b40250511c9c4ea597f7de2929693ae30959977f6e4097
7
+ data.tar.gz: 03e17274e463f8e60fe50c9d22220a5f930570da6000802d227a9da2f28b4a51e84ba06af1009d6e32479f2a54a3ea95c21999ec2b06d0f18d00d94ecd17c5aa
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jean-Francis Bastien
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
+ [![Gem Version](https://badge.fury.io/rb/solid_apm.svg)](https://badge.fury.io/rb/solid_apm)
2
+
1
3
  # SolidApm
2
4
  Rails engine to manage APM data without using a third party service.
3
5
 
4
- <img src="https://github.com/Bhacaz/solid_apm/assets/7858787/b83a4768-dbff-4c1c-8972-4b9db1092c99" width="400px">
5
- <img src="https://github.com/Bhacaz/solid_apm/assets/7858787/87696866-1fb3-46d6-91ae-0137cc7da578" width="400px">
6
-
6
+ <img src="./docs/img.png" width="600px">
7
+ <img src="./docs/img_1.png" width="600px">
7
8
 
8
9
  ## Installation
9
10
 
@@ -21,6 +22,10 @@ Rails.application.routes.draw do
21
22
  end
22
23
  ```
23
24
 
25
+ Routing constraint can be use to authorize access.
26
+ See [Routing constraint](https://guides.rubyonrails.org/routing.html#advanced-constraints)
27
+ for more information.
28
+
24
29
  Configure the database connection:
25
30
  ```ruby
26
31
  # config/initializers/solid_apm.rb
@@ -36,22 +41,38 @@ DATABASE=solid_apm bin/rails solid_apm:install:migrations
36
41
 
37
42
  Go to `http://localhost:3000/solid_apm` and start monitoring your application.
38
43
 
44
+ Add context
45
+
46
+ ```ruby
47
+ class ApplicationController
48
+ before_action do
49
+ SolidApm.set_context(user_id: current_user&.id)
50
+ end
51
+ end
52
+ ```
53
+
39
54
  ## TODOs
40
55
 
41
56
  ### Features
42
57
 
43
- - [ ] Ignore `/solid_apm` requests
44
58
  - [ ] Better handle subscribing to ActiveSupport notifications
45
- - [ ] Add methods to add context to the transaction (i.e. `SolidApm.add_context(user_id: 1)`)
59
+ - [ ] Custom events
46
60
 
47
61
  ### Interface
48
62
 
49
63
  - [ ] Paginate transactions list
50
64
  - [ ] Allow date range transactions index
51
- - [ ] Display transaction as aggregated data with avg latency, tpm and impact (Relative Avg. duration * transactions per minute)
52
65
 
53
66
  ## Contributing
54
67
  Contribution directions go here.
55
68
 
69
+ ## Release
70
+
71
+ ```shell
72
+ gem bump -v minor
73
+ bundle install && git add Gemfile.lock && git commit --amend --no-edit && git push
74
+ gem tag -p
75
+ ```
76
+
56
77
  ## License
57
78
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -13,7 +13,9 @@ import {
13
13
  // Connects to data-controller="transaction-chart"
14
14
  window.Stimulus.register('transaction-chart',
15
15
  class extends Controller {
16
- connect() {
16
+ static values = { name: String }
17
+
18
+ connect() {
17
19
  console.log('Connected')
18
20
  var options = {
19
21
  chart: {
@@ -26,6 +28,9 @@ window.Stimulus.register('transaction-chart',
26
28
  xaxis: {
27
29
  type: 'datetime'
28
30
  },
31
+ dataLabels: {
32
+ enabled: false
33
+ },
29
34
  tooltip: {
30
35
  x: {
31
36
  formatter: function (value) {
@@ -34,7 +39,20 @@ window.Stimulus.register('transaction-chart',
34
39
  }
35
40
  }
36
41
  }
37
- fetch('transactions.json')
42
+
43
+ let path = window.location.pathname.includes('transactions') ? 'count_by_minutes' : 'transactions/count_by_minutes';
44
+ path = path + "?";
45
+ if (this.nameValue) {
46
+ path = path + "name=" + encodeURIComponent(this.nameValue);
47
+ }
48
+
49
+ const fromValue = document.querySelector('input[name="from_value"]').value
50
+ const fromUnit = document.querySelector('select[name="from_unit"]').value;
51
+ if (fromValue && fromUnit) {
52
+ path = path + "&from_value=" + fromValue + "&from_unit=" + fromUnit;
53
+ }
54
+
55
+ fetch(path)
38
56
  .then(response => response.json())
39
57
  .then(data => {
40
58
  const transformedData = []
@@ -2,21 +2,38 @@
2
2
 
3
3
  module SolidApm
4
4
  class TransactionsController < ApplicationController
5
+ TransactionAggregation = Struct.new(:name, :tmp, :latency, :percentile_95, :impact)
6
+
5
7
  def index
6
- @transactions = Transaction.all.order(timestamp: :desc).limit(10)
7
-
8
- # uri = URI('https://dog-api.kinduff.com/api/facts')
9
- # response = Net::HTTP.get(uri)
10
- # @dog_fact = JSON.parse(response)
11
- #
12
- # Rails.cache.fetch('dog_fact', expires_in: 1.minutes) do
13
- # 'This is a dog fact!'
14
- # end
15
-
16
- respond_to do |format|
17
- format.html
18
- format.json { render json: transactions_count_by_minutes }
8
+ @aggregated_transactions = Transaction.where(created_at: from_to_range).group_by(&:name)
9
+ @aggregated_transactions.transform_values! do |transactions|
10
+ latency = transactions.map(&:duration).sum / transactions.size
11
+ tmp = transactions.size.to_f / ((from_to_range.end - from_to_range.begin) / 60).to_i
12
+ impact = latency * tmp
13
+ percentile_95 = transactions[transactions.size * 0.95].duration
14
+ TransactionAggregation.new(
15
+ transactions.first.name,
16
+ tmp,
17
+ latency,
18
+ percentile_95,
19
+ impact
20
+ )
21
+ end
22
+ # Find the maximum and minimum impact values
23
+ max_impact = @aggregated_transactions.values.max_by(&:impact).impact
24
+ min_impact = @aggregated_transactions.values.min_by(&:impact).impact
25
+
26
+ # Normalize impact 0-100
27
+ @aggregated_transactions.each do |_, aggregation|
28
+ normalized_impact = ((aggregation.impact - min_impact) / (max_impact - min_impact)) * 100
29
+ normalized_impact = 0 if normalized_impact.nan?
30
+ aggregation.impact = normalized_impact.to_i || 0
19
31
  end
32
+ @aggregated_transactions = @aggregated_transactions.sort_by { |_, v| -v.impact }.to_h
33
+ end
34
+
35
+ def show_by_name
36
+ @transactions = Transaction.where(name: params[:name]).order(timestamp: :desc).limit(20)
20
37
  end
21
38
 
22
39
  def show
@@ -29,13 +46,26 @@ module SolidApm
29
46
  render json: @spans
30
47
  end
31
48
 
49
+ def count_by_minutes
50
+ scope = Transaction.all.order(timestamp: :desc)
51
+ .where(created_at: from_to_range)
52
+
53
+ if params[:name].present?
54
+ scope = scope.where(name: params[:name])
55
+ end
56
+
57
+ render json: scope.group_by { |t| t.created_at.beginning_of_minute }
58
+ .transform_values!(&:count)
59
+ end
60
+
32
61
  private
33
62
 
34
- def transactions_count_by_minutes
35
- Transaction.all.order(timestamp: :desc)
36
- .where(created_at: 1.hour.ago..)
37
- .group_by { |t| t.created_at.beginning_of_minute }
38
- .transform_values!(&:count)
63
+ def from_to_range
64
+ params[:from_value] ||= 60
65
+ params[:from_unit] ||= 'minutes'
66
+ from = params[:from_value].to_i.public_send(params[:from_unit].to_sym).ago
67
+ to = Time.current
68
+ (from..to)
39
69
  end
40
70
  end
41
- end
71
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidApm
4
+ module SpanSubscriber
5
+ class ActionDispatch < Base
6
+ PATTERN = /\w+\.action_dispatch/.freeze
7
+ def self.subscribe
8
+ super do |name, start, finish, id, payload|
9
+ transaction = SpanSubscriber::Base.transaction
10
+ transaction.name = "#{payload[:request].controller_class}##{payload[:request].path_parameters[:action]}"
11
+ transaction.end_time = finish
12
+ transaction.duration = ((transaction.end_time.to_f - transaction.timestamp.to_f) * 1000).round(6)
13
+ transaction.metadata = {
14
+ params: payload[:request].params.except(:controller, :action),
15
+ context: SpanSubscriber::Base.context
16
+ }
17
+ SpanSubscriber::Base.context = nil
18
+ end
19
+ end
20
+
21
+ def summary(payload)
22
+ "#{payload[:request].controller_class}##{payload[:request].path_parameters[:action]}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,37 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidApm
4
- module SpanSubscriber
5
- class ActionViewRender < Base
6
- PATTERN = /^render_.+\.action_view/
4
+ module SpanSubscriber
5
+ class ActionViewRender < Base
6
+ PATTERN = /^render_.+\.action_view/
7
7
 
8
- def summary(payload)
9
- identifier = payload[:identifier]
10
- sanitize_path(identifier)
11
- end
8
+ def summary(payload)
9
+ identifier = payload[:identifier]
10
+ sanitize_path(identifier)
11
+ end
12
12
 
13
- private
13
+ private
14
14
 
15
- def sanitize_path(path)
16
- if path.start_with? Rails.root.to_s
17
- app_path(path)
18
- else
19
- gem_path(path)
15
+ def sanitize_path(path)
16
+ if path.start_with? Rails.root.to_s
17
+ app_path(path)
18
+ else
19
+ gem_path(path)
20
+ end
20
21
  end
21
- end
22
22
 
23
- def app_path(path)
24
- return unless path.start_with? Rails.root.to_s
23
+ def app_path(path)
24
+ return unless path.start_with? Rails.root.to_s
25
25
 
26
- format '$APP_PATH%s', path[Rails.root.to_s.length, path.length]
27
- end
26
+ format '$APP_PATH%s', path[Rails.root.to_s.length, path.length]
27
+ end
28
28
 
29
- def gem_path(path)
30
- root = Gem.path.find { |gp| path.start_with? gp }
31
- return unless root
29
+ def gem_path(path)
30
+ root = Gem.path.find { |gp| path.start_with? gp }
31
+ return unless root
32
32
 
33
- format '$GEM_PATH%s', path[root.length, path.length]
33
+ format '$GEM_PATH%s', path[root.length, path.length]
34
+ end
34
35
  end
35
36
  end
36
- end
37
37
  end
@@ -1,55 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
  module SolidApm
3
- module SpanSubscriber
4
- class Base
5
- # PATTERN = /.*/
3
+ module SpanSubscriber
4
+ class Base
5
+ # PATTERN = /.*/
6
6
 
7
- class_attribute :subscribers, default: Set.new
8
- thread_cattr_accessor :transaction
9
- thread_cattr_accessor :spans
7
+ class_attribute :subscribers, default: Set.new
8
+ thread_cattr_accessor :transaction
9
+ thread_cattr_accessor :spans
10
+ thread_cattr_accessor :context
10
11
 
11
- def self.inherited(subclass)
12
- subscribers << subclass
13
- end
12
+ def self.inherited(subclass)
13
+ subscribers << subclass
14
+ end
14
15
 
15
- def self.subscribe!
16
- subscribers.each(&:subscribe)
17
- end
16
+ def self.subscribe!
17
+ subscribers.each(&:subscribe)
18
+ end
18
19
 
19
- def self.subscribe
20
- ActiveSupport::Notifications.subscribe(self::PATTERN) do |name, start, finish, id, payload|
21
- next unless SpanSubscriber::Base.transaction
22
-
23
- subtype, type = name.split('.')
24
- duration = ((finish.to_f - start.to_f) * 1000).round(6)
25
-
26
- span = {
27
- uuid: SecureRandom.uuid,
28
- sequence: SpanSubscriber::Base.spans.size + 1,
29
- timestamp: start,
30
- end_time: finish,
31
- duration: duration,
32
- name: name,
33
- type: type,
34
- subtype: subtype,
35
- summary: self.new.summary(payload),
36
- }
37
-
38
- SpanSubscriber::Base.spans << span
20
+ def self.subscribe
21
+ ActiveSupport::Notifications.subscribe(self::PATTERN) do |name, start, finish, id, payload|
22
+ next unless SpanSubscriber::Base.transaction
23
+
24
+ subtype, type = name.split('.')
25
+ duration = ((finish.to_f - start.to_f) * 1000).round(6)
26
+
27
+ span = {
28
+ uuid: SecureRandom.uuid,
29
+ sequence: SpanSubscriber::Base.spans.size + 1,
30
+ timestamp: start,
31
+ end_time: finish,
32
+ duration: duration,
33
+ name: name,
34
+ type: type,
35
+ subtype: subtype,
36
+ summary: self.new.summary(payload),
37
+ }
38
+
39
+ SpanSubscriber::Base.spans << span
40
+
41
+ # Allow the subscriber to yield additional spans, like ending the transaction
42
+ yield(name, start, finish, id, payload) if block_given?
43
+ end
39
44
  end
40
- end
41
45
 
42
- # def summary(payload)
43
- # if payload.is_a?(Hash)
44
- # payload.first.last.inspect
45
- # else
46
- # payload.inspect
47
- # end
48
- # end
46
+ # def summary(payload)
47
+ # if payload.is_a?(Hash)
48
+ # payload.first.last.inspect
49
+ # else
50
+ # payload.inspect
51
+ # end
52
+ # end
49
53
 
50
- # private_class_method :subscribe
54
+ # private_class_method :subscribe
55
+ end
51
56
  end
52
57
  end
53
- end
54
58
 
55
59
  Dir[File.join(__dir__, '*.rb')].sort.each { |file| require file }
@@ -1,29 +1,47 @@
1
1
  <h1 class="title">Transactions</h1>
2
2
 
3
- <h2 class="title is-4 has-text-grey">Last hour</h2>
3
+ <%= form_with path: transactions_path, method: :get do |f| %>
4
+ <div class="is-flex is-flex-direction-row is-justify-content-center is-align-items-center" style="gap: 1em">
5
+ <%= f.label 'From' %>
6
+ <%= f.number_field :from_value, value: params[:from_value] || 60, min: 1, class: 'input', style: 'width: 6em' %>
7
+ <div class="select">
8
+ <%= f.select :from_unit, {
9
+ "minutes" => "minutes",
10
+ "hours" => "hours",
11
+ "days" => "days",
12
+ "weeks" => "weeks",
13
+ "months" => "months",
14
+ "years" => "years"
15
+ }, {selected: params[:from_unit] || 'minutes' } %>
16
+ </div>
17
+ <span class="has-text-white-ter">ago</span>
18
+ <b>→</b>
19
+ <span class="has-text-white-ter">now</span>
20
+ <%= f.submit 'Apply', class: 'button' %>
21
+ </div>
22
+ <% end %>
4
23
  <div data-controller="transaction-chart"></div>
5
24
 
6
- <table class="table">
25
+ <table class="table is-fullwidth">
7
26
  <thead>
8
27
  <tr>
9
- <% SolidApm::Transaction.attribute_names.each do |attribute| %>
10
- <% next if attribute.to_s.end_with?('_at') %>
11
- <th scope="col"><%= attribute.humanize %></th>
12
- <% end %>
28
+ <th>Name</th>
29
+ <th>Latency</th>
30
+ <th>tmp</th>
31
+ <th>95p</th>
32
+ <th>Impact</th>
13
33
  </tr>
14
34
  </thead>
15
- <tbody>
16
- <% @transactions.each do |transaction| %>
35
+
36
+ <% @aggregated_transactions.each do |name, aggregation| %>
17
37
  <tr>
18
- <% transaction.attributes.each do |attribute| %>
19
- <% next if attribute[0].to_s.end_with?('_at') %>
20
- <% if attribute[0] == 'uuid' %>
21
- <td><%= link_to attribute[1], transaction %></td>
22
- <% else %>
23
- <td><%= attribute[1] %></td>
24
- <% end %>
25
- <% end %>
38
+ <td><%= link_to name, transaction_by_name_path(name) %></td>
39
+ <td><%= aggregation.latency.round(2) %> ms</td>
40
+ <td><%= aggregation.tmp.round(2) %></td>
41
+ <td><%= aggregation.percentile_95.round(2) %> ms</td>
42
+ <td>
43
+ <progress class="progress is-warning" value="<%= aggregation.impact %>" max="100"></progress>
44
+ </td>
26
45
  </tr>
27
46
  <% end %>
28
- </tbody>
29
47
  </table>
@@ -3,6 +3,7 @@
3
3
  <h2 class="title is-6"><span class="has-text-grey-dark">Trace ID:</span> <%= @transaction.uuid %></h2>
4
4
  <h2 class="title is-6"><span class="has-text-grey-dark">Timestamp:</span> <%= @transaction.timestamp %></h2>
5
5
  <h2 class="title is-6"><span class="has-text-grey-dark">Duration:</span> <%= @transaction.duration %> ms</h2>
6
+ <h2 class="title is-6"><span class="has-text-grey-dark">Unix minute:</span> <%= @transaction.unix_minute %></h2>
6
7
  <h2 class="title is-6"><span class="has-text-grey-dark">Metadata:</span> <%= @transaction.metadata %></h2>
7
8
 
8
9
  <%= render template: 'solid_apm/spans/index', collection: @transaction.spans, locals: { spans: @transaction.spans } %>
@@ -0,0 +1,27 @@
1
+ <h1 class="title is-3"><%= params[:name] %></h1>
2
+ <div data-controller="transaction-chart" data-transaction-chart-name-value="<%= params[:name] %>"></div>
3
+
4
+ <table class="table">
5
+ <thead>
6
+ <tr>
7
+ <% SolidApm::Transaction.attribute_names.each do |attribute| %>
8
+ <% next if attribute.to_s.end_with?('_at') %>
9
+ <th scope="col"><%= attribute.humanize %></th>
10
+ <% end %>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @transactions.each do |transaction| %>
15
+ <tr>
16
+ <% transaction.attributes.each do |attribute| %>
17
+ <% next if attribute[0].to_s.end_with?('_at') %>
18
+ <% if attribute[0] == 'uuid' %>
19
+ <td><%= link_to attribute[1], transaction_path(transaction) %></td>
20
+ <% else %>
21
+ <td><%= attribute[1] %></td>
22
+ <% end %>
23
+ <% end %>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
data/config/routes.rb CHANGED
@@ -2,6 +2,12 @@ SolidApm::Engine.routes.draw do
2
2
  root 'transactions#index'
3
3
 
4
4
  get 'transactions', to: 'transactions#index'
5
- get 'transactions/:id', to: 'transactions#show', as: 'transaction'
5
+ get 'transactions/count_by_minutes',
6
+ to: 'transactions#count_by_minutes',
7
+ as: 'transactions_count_by_minutes',
8
+ default: { format: 'json' }
9
+
10
+ get 'transactions/:id', to: 'transactions#show', as: 'transaction', constraints: { id: /\d+/ }
11
+ get 'transactions/:name', to: 'transactions#show_by_name', as: 'transaction_by_name'
6
12
  get 'transactions/:id/spans', to: 'transactions#spans', as: 'transaction_spans'
7
13
  end
@@ -11,24 +11,6 @@ module SolidApm
11
11
  end
12
12
 
13
13
  config.after_initialize do
14
- ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
15
- SpanSubscriber::Base.transaction = Transaction.new(
16
- uuid: SecureRandom.uuid,
17
- timestamp: start,
18
- type: 'request',
19
- name: "#{payload[:controller]}##{payload[:action]}",
20
- metadata: { params: payload[:request].params.except(:controller, :action) }
21
- )
22
- SpanSubscriber::Base.spans = []
23
- end
24
-
25
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, start, finish, id, payload|
26
- # Set the end time and duration of the transaction with the process_action event
27
- transaction = SpanSubscriber::Base.transaction
28
- transaction.end_time = finish
29
- transaction.duration = ((transaction.end_time.to_f - transaction.timestamp.to_f) * 1000).round(6)
30
- end
31
-
32
14
  SpanSubscriber::Base.subscribe!
33
15
  end
34
16
  end
@@ -1,28 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidApm
4
- class Middleware
4
+ class Middleware
5
5
  def initialize(app)
6
6
  @app = app
7
7
  end
8
8
 
9
9
  def call(env)
10
- env['rack.after_reply'] ||= []
11
- env['rack.after_reply'] << ->() do
12
- self.class.call
13
- rescue StandardError => e
14
- Rails.logger.error e
15
- Rails.logger.error e.backtrace&.join("\n")
16
- end
10
+ self.class.init_transaction
11
+ status, headers, body = @app.call(env)
17
12
 
18
- @app.call(env)
13
+ env['rack.after_reply'] ||= []
14
+ env['rack.after_reply'] << ->() do
15
+ self.class.call
16
+ rescue StandardError => e
17
+ Rails.logger.error e
18
+ Rails.logger.error e.backtrace&.join("\n")
19
+ end
20
+ [status, headers, body]
19
21
  end
20
22
 
21
23
  def self.call
22
24
  transaction = SpanSubscriber::Base.transaction
23
- return unless transaction
24
-
25
25
  SpanSubscriber::Base.transaction = nil
26
+ if transaction.nil? || transaction.name.start_with?('SolidApm::')
27
+ SpanSubscriber::Base.spans = nil
28
+ return
29
+ end
30
+
26
31
  ApplicationRecord.transaction do
27
32
  transaction.save!
28
33
 
@@ -33,5 +38,17 @@ class Middleware
33
38
  end
34
39
  SpanSubscriber::Base.spans = nil
35
40
  end
41
+
42
+ def self.init_transaction
43
+ now = Time.zone.now
44
+ SpanSubscriber::Base.transaction = Transaction.new(
45
+ uuid: SecureRandom.uuid,
46
+ timestamp: now,
47
+ unix_minute: (now.to_f / 60).to_i,
48
+ type: 'request'
49
+ )
50
+ SpanSubscriber::Base.spans = []
51
+ SpanSubscriber::Base.transaction
52
+ end
36
53
  end
37
54
  end
@@ -1,3 +1,3 @@
1
1
  module SolidApm
2
- VERSION = "0.1.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/solid_apm.rb CHANGED
@@ -3,4 +3,8 @@ require "solid_apm/engine"
3
3
 
4
4
  module SolidApm
5
5
  mattr_accessor :connects_to
6
+
7
+ def self.set_context(context)
8
+ SpanSubscriber::Base.context = context
9
+ end
6
10
  end
metadata CHANGED
@@ -1,36 +1,79 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Francis Bastien
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-10 00:00:00.000000000 Z
11
+ date: 2024-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
14
+ name: actionpack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.3.2
19
+ version: '7.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 7.1.3.2
27
- description: Description of SolidApm.
26
+ version: '7.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionview
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '7.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: railties
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.1'
69
+ description: SolidApm allow you to monitor your application without any external service.
28
70
  email:
29
71
  - bhacaz@gmail.com
30
72
  executables: []
31
73
  extensions: []
32
74
  extra_rdoc_files: []
33
75
  files:
76
+ - LICENSE
34
77
  - README.md
35
78
  - Rakefile
36
79
  - app/assets/config/solid_apm_manifest.js
@@ -44,7 +87,7 @@ files:
44
87
  - app/jobs/solid_apm/application_job.rb
45
88
  - app/models/solid_apm/application_record.rb
46
89
  - app/models/solid_apm/span.rb
47
- - app/models/solid_apm/span_subscriber/action_controller.rb
90
+ - app/models/solid_apm/span_subscriber/action_dispatch.rb
48
91
  - app/models/solid_apm/span_subscriber/action_view_render.rb
49
92
  - app/models/solid_apm/span_subscriber/active_record_sql.rb
50
93
  - app/models/solid_apm/span_subscriber/active_support_cache.rb
@@ -56,6 +99,7 @@ files:
56
99
  - app/views/solid_apm/spans/index.html.erb
57
100
  - app/views/solid_apm/transactions/index.html.erb
58
101
  - app/views/solid_apm/transactions/show.html.erb
102
+ - app/views/solid_apm/transactions/show_by_name.html.erb
59
103
  - config/routes.rb
60
104
  - db/migrate/20240608015633_create_solid_apm_transactions.rb
61
105
  - db/migrate/20240608021940_create_solid_apm_spans.rb
@@ -68,7 +112,9 @@ homepage: https://github.com/Bhacaz/solid_apm
68
112
  licenses: []
69
113
  metadata:
70
114
  homepage_uri: https://github.com/Bhacaz/solid_apm
71
- post_install_message:
115
+ source_code_uri: https://github.com/Bhacaz/solid_apm
116
+ changelog_uri: https://github.com/Bhacaz/solid_apm/releases
117
+ post_install_message:
72
118
  rdoc_options: []
73
119
  require_paths:
74
120
  - lib
@@ -76,15 +122,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
122
  requirements:
77
123
  - - ">="
78
124
  - !ruby/object:Gem::Version
79
- version: '0'
125
+ version: '3.2'
80
126
  required_rubygems_version: !ruby/object:Gem::Requirement
81
127
  requirements:
82
128
  - - ">="
83
129
  - !ruby/object:Gem::Version
84
130
  version: '0'
85
131
  requirements: []
86
- rubygems_version: 3.5.9
87
- signing_key:
132
+ rubygems_version: 3.5.11
133
+ signing_key:
88
134
  specification_version: 4
89
- summary: Summary of SolidApm.
135
+ summary: SolidApm is a DB base engine for Application Performance Monitoring.
90
136
  test_files: []
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolidApm
4
- module SpanSubscriber
5
- class ActionController < Base
6
- PATTERN = 'process_action.action_controller'
7
-
8
- def summary(payload)
9
- "#{payload[:controller]}##{payload[:action]}"
10
- end
11
- end
12
- end
13
- end