solid_apm 0.1.0 → 0.3.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/LICENSE +21 -0
- data/README.md +27 -6
- data/app/assets/javascripts/solid_apm/controllers/transaction-chart_controller.js +10 -2
- data/app/controllers/solid_apm/transactions_controller.rb +37 -20
- data/app/models/solid_apm/span_subscriber/action_dispatch.rb +26 -0
- data/app/models/solid_apm/span_subscriber/action_view_render.rb +23 -23
- data/app/models/solid_apm/span_subscriber/base.rb +46 -42
- data/app/views/solid_apm/transactions/index.html.erb +13 -16
- data/app/views/solid_apm/transactions/show.html.erb +1 -0
- data/app/views/solid_apm/transactions/show_by_name.html.erb +27 -0
- data/config/routes.rb +7 -1
- data/lib/solid_apm/engine.rb +0 -18
- data/lib/solid_apm/middleware.rb +28 -11
- data/lib/solid_apm/version.rb +1 -1
- data/lib/solid_apm.rb +4 -0
- metadata +61 -15
- data/app/models/solid_apm/span_subscriber/action_controller.rb +0 -13
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c6233330338e9044097e1315840f1bbc7f2cdeb1677df3dac3261c2a50666046
         | 
| 4 | 
            +
              data.tar.gz: 37f306cf60295e74485092216c4bdbe2ba2c824ce08d279424e15c631543b33f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 18d46f142b8bc401acc9dcd3417c9782d04a6e2beabda8d3c7dbafd62429f8bcf13e69ecf2e7fe51049f17da8b35411c3b1239c3f9476950590917990d0c7aba
         | 
| 7 | 
            +
              data.tar.gz: a0140e3fdb51254f8c615ed7cd4002f851d0c1ef11b71d408c4966581b1638f475ada593977a53761631493201e53641df382b9c8d9c10598935fe7ae8b50da3
         | 
    
        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 | 
            +
            [](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=" | 
| 5 | 
            -
            <img src=" | 
| 6 | 
            -
             | 
| 6 | 
            +
            <img src="./docs/img.png" width="400px">
         | 
| 7 | 
            +
            <img src="./docs/img_1.png" width="400px">
         | 
| 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 | 
            -
            - [ ]  | 
| 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 . && git commit --amend --no-edit
         | 
| 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 | 
            -
             | 
| 16 | 
            +
                static values = { name: String }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                connect() {
         | 
| 17 19 | 
             
                console.log('Connected')
         | 
| 18 20 | 
             
                var options = {
         | 
| 19 21 | 
             
                  chart: {
         | 
| @@ -34,7 +36,13 @@ window.Stimulus.register('transaction-chart', | |
| 34 36 | 
             
                    }
         | 
| 35 37 | 
             
                  }
         | 
| 36 38 | 
             
                }
         | 
| 37 | 
            -
             | 
| 39 | 
            +
             | 
| 40 | 
            +
                let path = 'transactions/count_by_minutes'
         | 
| 41 | 
            +
                if (this.nameValue) {
         | 
| 42 | 
            +
                  path = path + "?name=" + encodeURIComponent(this.nameValue);
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                fetch(path)
         | 
| 38 46 | 
             
                  .then(response => response.json())
         | 
| 39 47 | 
             
                  .then(data => {
         | 
| 40 48 | 
             
                    const transformedData = []
         | 
| @@ -2,21 +2,35 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module SolidApm
         | 
| 4 4 | 
             
              class TransactionsController < ApplicationController
         | 
| 5 | 
            +
                TransactionAggregation = Struct.new(:name, :tmp, :latency, :impact)
         | 
| 6 | 
            +
             | 
| 5 7 | 
             
                def index
         | 
| 6 | 
            -
                  @ | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 8 | 
            +
                  @aggregated_transactions = Transaction.where(created_at: 1.hour.ago..).group_by(&:name)
         | 
| 9 | 
            +
                  @aggregated_transactions.transform_values! do |transactions|
         | 
| 10 | 
            +
                    latency = transactions.map(&:duration).sum / transactions.size
         | 
| 11 | 
            +
                    tmp = transactions.size.to_f / 60
         | 
| 12 | 
            +
                    impact = latency * tmp
         | 
| 13 | 
            +
                    TransactionAggregation.new(
         | 
| 14 | 
            +
                      transactions.first.name,
         | 
| 15 | 
            +
                      tmp,
         | 
| 16 | 
            +
                      latency,
         | 
| 17 | 
            +
                      impact
         | 
| 18 | 
            +
                    )
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                  # Find the maximum and minimum impact values
         | 
| 21 | 
            +
                  max_impact = @aggregated_transactions.values.max_by(&:impact).impact
         | 
| 22 | 
            +
                  min_impact = @aggregated_transactions.values.min_by(&:impact).impact
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # Normalize impact 0-100
         | 
| 25 | 
            +
                  @aggregated_transactions.each do |_, aggregation|
         | 
| 26 | 
            +
                    normalized_impact = ((aggregation.impact - min_impact) / (max_impact - min_impact)) * 100
         | 
| 27 | 
            +
                    aggregation.impact = normalized_impact.to_i
         | 
| 19 28 | 
             
                  end
         | 
| 29 | 
            +
                  @aggregated_transactions = @aggregated_transactions.sort_by { |_, v| -v.impact }.to_h
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def show_by_name
         | 
| 33 | 
            +
                  @transactions = Transaction.where(name: params[:name]).order(timestamp: :desc).limit(20)
         | 
| 20 34 | 
             
                end
         | 
| 21 35 |  | 
| 22 36 | 
             
                def show
         | 
| @@ -29,13 +43,16 @@ module SolidApm | |
| 29 43 | 
             
                  render json: @spans
         | 
| 30 44 | 
             
                end
         | 
| 31 45 |  | 
| 32 | 
            -
                 | 
| 33 | 
            -
             | 
| 34 | 
            -
                def transactions_count_by_minutes
         | 
| 35 | 
            -
                  Transaction.all.order(timestamp: :desc)
         | 
| 46 | 
            +
                def count_by_minutes
         | 
| 47 | 
            +
                  scope = Transaction.all.order(timestamp: :desc)
         | 
| 36 48 | 
             
                             .where(created_at: 1.hour.ago..)
         | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 49 | 
            +
             | 
| 50 | 
            +
                  if params[:name].present?
         | 
| 51 | 
            +
                    scope = scope.where(name: params[:name])
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  render json: scope.group_by { |t| t.created_at.beginning_of_minute }
         | 
| 55 | 
            +
                       .transform_values!(&:count)
         | 
| 39 56 | 
             
                end
         | 
| 40 57 | 
             
              end
         | 
| 41 | 
            -
            end
         | 
| 58 | 
            +
            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 | 
            -
             | 
| 6 | 
            -
             | 
| 4 | 
            +
              module SpanSubscriber
         | 
| 5 | 
            +
                class ActionViewRender < Base
         | 
| 6 | 
            +
                  PATTERN = /^render_.+\.action_view/
         | 
| 7 7 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 8 | 
            +
                  def summary(payload)
         | 
| 9 | 
            +
                    identifier = payload[:identifier]
         | 
| 10 | 
            +
                    sanitize_path(identifier)
         | 
| 11 | 
            +
                  end
         | 
| 12 12 |  | 
| 13 | 
            -
             | 
| 13 | 
            +
                  private
         | 
| 14 14 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 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 | 
            -
             | 
| 24 | 
            -
             | 
| 23 | 
            +
                  def app_path(path)
         | 
| 24 | 
            +
                    return unless path.start_with? Rails.root.to_s
         | 
| 25 25 |  | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 26 | 
            +
                    format '$APP_PATH%s', path[Rails.root.to_s.length, path.length]
         | 
| 27 | 
            +
                  end
         | 
| 28 28 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 29 | 
            +
                  def gem_path(path)
         | 
| 30 | 
            +
                    root = Gem.path.find { |gp| path.start_with? gp }
         | 
| 31 | 
            +
                    return unless root
         | 
| 32 32 |  | 
| 33 | 
            -
             | 
| 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 | 
            -
             | 
| 5 | 
            -
             | 
| 3 | 
            +
              module SpanSubscriber
         | 
| 4 | 
            +
                class Base
         | 
| 5 | 
            +
                  # PATTERN = /.*/
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 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 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 12 | 
            +
                  def self.inherited(subclass)
         | 
| 13 | 
            +
                    subscribers << subclass
         | 
| 14 | 
            +
                  end
         | 
| 14 15 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 16 | 
            +
                  def self.subscribe!
         | 
| 17 | 
            +
                    subscribers.each(&:subscribe)
         | 
| 18 | 
            +
                  end
         | 
| 18 19 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 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 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 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 | 
            -
             | 
| 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 }
         | 
| @@ -3,27 +3,24 @@ | |
| 3 3 | 
             
            <h2 class="title is-4 has-text-grey">Last hour</h2>
         | 
| 4 4 | 
             
            <div data-controller="transaction-chart"></div>
         | 
| 5 5 |  | 
| 6 | 
            -
            <table class="table">
         | 
| 6 | 
            +
            <table class="table is-fullwidth">
         | 
| 7 7 | 
             
              <thead>
         | 
| 8 8 | 
             
                <tr>
         | 
| 9 | 
            -
                   | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                   | 
| 9 | 
            +
                  <th>Name</th>
         | 
| 10 | 
            +
                  <th>Latency</th>
         | 
| 11 | 
            +
                  <th>tmp</th>
         | 
| 12 | 
            +
                  <th>Impact</th>
         | 
| 13 13 | 
             
                </tr>
         | 
| 14 14 | 
             
              </thead>
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              <% @ | 
| 15 | 
            +
             | 
| 16 | 
            +
              <% @aggregated_transactions.each do |name, aggregation| %>
         | 
| 17 17 | 
             
                <tr>
         | 
| 18 | 
            -
                   | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                     | 
| 23 | 
            -
             | 
| 24 | 
            -
                    <% end %>
         | 
| 25 | 
            -
                  <% end %>
         | 
| 18 | 
            +
                  <td><%= link_to name, transaction_by_name_path(name) %></td>
         | 
| 19 | 
            +
                  <td><%= aggregation.latency.round(2) %> ms</td>
         | 
| 20 | 
            +
                  <td><%= aggregation.tmp.round(2) %></td>
         | 
| 21 | 
            +
                  <td>
         | 
| 22 | 
            +
                    <progress class="progress is-warning" value="<%= aggregation.impact %>" max="100"></progress>
         | 
| 23 | 
            +
                  </td>
         | 
| 26 24 | 
             
                </tr>
         | 
| 27 25 | 
             
              <% end %>
         | 
| 28 | 
            -
              </tbody>
         | 
| 29 26 | 
             
            </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 | 
| 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
         | 
    
        data/lib/solid_apm/engine.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/solid_apm/middleware.rb
    CHANGED
    
    | @@ -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 | 
            -
                   | 
| 11 | 
            -
                   | 
| 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 | 
            -
             | 
| 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
         | 
    
        data/lib/solid_apm/version.rb
    CHANGED
    
    
    
        data/lib/solid_apm.rb
    CHANGED
    
    
    
        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. | 
| 4 | 
            +
              version: 0.3.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- | 
| 11 | 
            +
            date: 2024-06-14 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name:  | 
| 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 | 
| 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 | 
| 27 | 
            -
             | 
| 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/ | 
| 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 | 
            -
             | 
| 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: ' | 
| 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. | 
| 87 | 
            -
            signing_key:
         | 
| 132 | 
            +
            rubygems_version: 3.5.11
         | 
| 133 | 
            +
            signing_key: 
         | 
| 88 134 | 
             
            specification_version: 4
         | 
| 89 | 
            -
            summary:  | 
| 135 | 
            +
            summary: SolidApm is a DB base engine for Application Performance Monitoring.
         | 
| 90 136 | 
             
            test_files: []
         |