solid_apm 0.7.1 → 0.8.1

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: 78900f85182065cd6b2fa85e601d3bae5808b7f4aa84d8896da5f772c733f0a4
4
- data.tar.gz: 6aa0655c7ac1f252ecf5e9b23a75f2a1ad5c19ce15b910a2fe49af7ee1fc1143
3
+ metadata.gz: 548f55d6caa0c8616d90f4ad6412cc4cab61f321497748300a66b5fa685ce6c5
4
+ data.tar.gz: 22c809b06a11521169d94077cc9fbc02e29eb724fc558e9afc399cf767f506e5
5
5
  SHA512:
6
- metadata.gz: 8939c3bf1123f69187abfa4ae11317019b0f962ad8c7591f82ada6c1eb68ba57bb246bcd21e611f79603fe29abeaf2ec848f4c9ee4cef278847383eccdb0839d
7
- data.tar.gz: 27ed6ea06120bdfaaded1747a4ec08020e0ce9dcc10fc36ba12f03765a5ba5073a191488a0ab32b830f0845a67feb966eabfcab255dd7fde8ca6fb5dee9229ab
6
+ metadata.gz: c3bd60eb649a8534929ff31438b97e0685010a7aaae7cbeed837e7ab808993f477290a80bc0b7ee9acb650f8bf6105e7c0c821dd3f14c0c200b9f43d52581d5e
7
+ data.tar.gz: dc50593ebd0efa6e94384a664e5b80994faaf9d326f7405f3b0d0d7b57e57049f2b644a6b40217c6f58bc1e587d563eda3aa42e5d95dd867efed8c6ea74e66b6
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidApm
4
+ class SpansController < ApplicationController
5
+ def show
6
+ @span = SolidApm::Span.find_by!(uuid: params[:uuid])
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,24 @@
1
1
  module SolidApm
2
2
  module ApplicationHelper
3
+ def span_type_color(span)
4
+ case span.type
5
+ when 'action_view'
6
+ '#00bcd4cc' # cyan
7
+ when 'action_dispatch'
8
+ '#4caf50cc' # green
9
+ when 'active_record'
10
+ '#ff9800cc' # orange
11
+ when 'active_support'
12
+ '#f44336cc' # red
13
+ when 'net_http'
14
+ '#9c27b0cc' # purple
15
+ when 'custom'
16
+ '#3f51b5cc' # indigo
17
+ else
18
+ '#9e9e9ecc' # grey
19
+ end
20
+ end
21
+
3
22
  def area_chart_options
4
23
  {
5
24
  module: true,
@@ -7,31 +7,8 @@ module SolidApm
7
7
 
8
8
  def summary(payload)
9
9
  identifier = payload[:identifier]
10
- sanitize_path(identifier)
11
- end
12
-
13
- private
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)
20
- end
21
- end
22
-
23
- def app_path(path)
24
- return unless path.start_with? Rails.root.to_s
25
-
26
- format '$APP_PATH%s', path[Rails.root.to_s.length, path.length]
27
- end
28
-
29
- def gem_path(path)
30
- root = Gem.path.find { |gp| path.start_with? gp }
31
- return unless root
32
-
33
- format '$GEM_PATH%s', path[root.length, path.length]
10
+ clean_trace(identifier)
34
11
  end
35
12
  end
36
13
  end
37
- end
14
+ end
@@ -2,7 +2,7 @@
2
2
  module SolidApm
3
3
  module SpanSubscriber
4
4
  class ActiveRecordSql < Base
5
- PATTERN = "sql.active_record"
5
+ PATTERN = /^(sql|transaction)\.active_record/.freeze
6
6
 
7
7
  def summary(payload)
8
8
  payload[:sql]
@@ -34,6 +34,7 @@ module SolidApm
34
34
  type: type,
35
35
  subtype: subtype,
36
36
  summary: self.new.summary(payload),
37
+ stacktrace: clean_trace(caller_locations)
37
38
  }
38
39
 
39
40
  SpanSubscriber::Base.spans << span
@@ -43,6 +44,30 @@ module SolidApm
43
44
  end
44
45
  end
45
46
 
47
+ def self.backtrace_cleaner
48
+ @backtrace_cleaner ||= begin
49
+ bc = ActiveSupport::BacktraceCleaner.new
50
+ bc.remove_filters!
51
+ gem_roots = Gem.path
52
+ [Rails.root, *gem_roots].each do |path|
53
+ bc.add_filter { |line| line.delete_prefix("#{path}/") }
54
+ end
55
+ bc
56
+ end
57
+ end
58
+
59
+ def self.clean_trace(backtrace)
60
+ if backtrace.is_a?(Array)
61
+ backtrace_cleaner.clean(backtrace)
62
+ else
63
+ backtrace_cleaner.clean([backtrace]).first
64
+ end
65
+ end
66
+
67
+ def clean_trace(backtrace)
68
+ self.class.clean_trace(backtrace)
69
+ end
70
+
46
71
  # def summary(payload)
47
72
  # if payload.is_a?(Hash)
48
73
  # payload.first.last.inspect
@@ -1,45 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
3
  module SolidApm
5
- module SpanSubscriber
6
- class NetHttp < Base
7
- PATTERN = 'request.net_http'
4
+ module SpanSubscriber
5
+ class NetHttp < Base
6
+ PATTERN = 'request.net_http'
8
7
 
9
- def summary(payload)
10
- payload
11
- end
12
-
13
- def self.subscribe
14
- if defined?(::Net::HTTP)
15
- ::Net::HTTP.prepend(NetHttpInstrumentationPrepend)
16
- super
8
+ def summary(payload)
9
+ payload
17
10
  end
18
- end
19
11
 
20
- # https://github.com/scoutapp/scout_apm_ruby/blob/3838109214503755c5cbd4caf78f6446adbe222f/lib/scout_apm/instruments/net_http.rb#L61
21
- module NetHttpInstrumentationPrepend
22
- def request(request, *args, &block)
23
- ActiveSupport::Notifications.instrument PATTERN, request_solid_apm_description(request) do
24
- super(request, *args, &block)
12
+ def self.subscribe
13
+ if defined?(::Net::HTTP)
14
+ ::Net::HTTP.prepend(NetHttpInstrumentationPrepend)
15
+ super
25
16
  end
26
17
  end
27
18
 
28
- def request_solid_apm_description(req)
29
- path = req.path
30
- path = path.path if path.respond_to?(:path)
31
-
32
- # Protect against a nil address value
33
- if @address.nil?
34
- return "No Address Found"
19
+ # https://github.com/scoutapp/scout_apm_ruby/blob/3838109214503755c5cbd4caf78f6446adbe222f/lib/scout_apm/instruments/net_http.rb#L61
20
+ module NetHttpInstrumentationPrepend
21
+ def request(request, *args, &block)
22
+ ActiveSupport::Notifications.instrument PATTERN, request_solid_apm_description(request) do
23
+ super(request, *args, &block)
24
+ end
35
25
  end
36
26
 
37
- max_length = 500
38
- req.method.upcase + " " + (@address + path.split('?').first)[0..(max_length - 1)]
39
- rescue
40
- ""
27
+ def request_solid_apm_description(req)
28
+ path = req.path
29
+ path = path.path if path.respond_to?(:path)
30
+
31
+ # Protect against a nil address value
32
+ if @address.nil?
33
+ return "No Address Found"
34
+ end
35
+
36
+ max_length = 500
37
+ req.method.upcase + " " + (@address + path.split('?').first)[0..(max_length - 1)]
38
+ rescue
39
+ ""
40
+ end
41
41
  end
42
42
  end
43
43
  end
44
44
  end
45
- end
@@ -0,0 +1,46 @@
1
+ <h1 class="title">Span Details</h1>
2
+
3
+ <h2 class="title is-6"><span class="has-text-grey">UUID:</span> <%= @span.uuid %></h2>
4
+ <h2 class="title is-6"><span class="has-text-grey">Timestamp:</span> <%= @span.timestamp %></h2>
5
+ <h2 class="title is-6"><span class="has-text-grey">Name:</span> <%= @span.name %></h2>
6
+ <h2 class="title is-6"><span class="has-text-grey">Type:</span> <%= @span.type %></h2>
7
+ <h2 class="title is-6"><span class="has-text-grey">Subtype:</span> <%= @span.subtype %></h2>
8
+ <h2 class="title is-6"><span class="has-text-grey">Summary:</span> <%= @span.summary %></h2>
9
+ <h2 class="title is-6"><span class="has-text-grey">Duration:</span> <%= @span.duration.round(2) %>ms</h2>
10
+
11
+ <h2 class="title is-4 pt-4">Stacktrace</h2>
12
+ <% if @span.stacktrace.present? %>
13
+ <button id="toggle-stacktrace" data-state="app-only">Show All</button>
14
+ <pre id="stacktrace" style="display: grid">
15
+ <% @span.stacktrace.each do |line| %>
16
+ <% source = line.start_with?('gems') ? 'other' : 'app' %>
17
+ <span class="stack-line" data-source="<%= source %>" style="<%= 'display:none;' if source == 'other' %>"><%= line %></span>
18
+ <% end %>
19
+ </pre>
20
+ <script>
21
+ document.addEventListener('DOMContentLoaded', function() {
22
+ var btn = document.getElementById('toggle-stacktrace');
23
+ btn.addEventListener('click', function() {
24
+ var state = btn.getAttribute('data-state');
25
+ var lines = document.querySelectorAll('#stacktrace .stack-line');
26
+ if (state === 'app-only') {
27
+ lines.forEach(function(line) {
28
+ line.style.display = 'inline-block';
29
+ });
30
+ btn.textContent = 'Show App Only';
31
+ btn.setAttribute('data-state', 'all');
32
+ } else {
33
+ lines.forEach(function(line) {
34
+ if (line.dataset.source === 'other') {
35
+ line.style.display = 'none';
36
+ }
37
+ });
38
+ btn.textContent = 'Show All';
39
+ btn.setAttribute('data-state', 'app-only');
40
+ }
41
+ });
42
+ });
43
+ </script>
44
+ <% else %>
45
+ <p>No stacktrace available.</p>
46
+ <% end %>
@@ -10,25 +10,63 @@
10
10
  <% max_end_time = @transaction.spans.maximum(:end_time) %>
11
11
  <% total_duration = max_end_time - min_start_time %>
12
12
 
13
+ <style>
14
+ .span-details {
15
+ position: absolute;
16
+ right: 0;
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: center;
20
+ padding-left: .5rem;
21
+ height: 100%;
22
+ max-width: 25em;
23
+ }
24
+
25
+ .span-bar {
26
+ position: relative;
27
+ border-radius: 5px;
28
+ }
29
+
30
+ .span-details .truncate {
31
+ text-overflow: ellipsis;
32
+ white-space: nowrap;
33
+ overflow: hidden;
34
+ }
35
+
36
+ .span-details .summary {
37
+ white-space: nowrap;
38
+ margin-top: .25rem;
39
+ }
40
+
41
+ .span-row {
42
+ border-bottom: 1px solid #333333;
43
+ padding: 0 1rem;
44
+ }
45
+
46
+ .span-row:last-child {
47
+ border-bottom: none;
48
+ }
49
+ </style>
50
+
13
51
  <div class="is-fullwidth">
14
52
  <% @transaction.spans.each do |span| %>
15
53
  <% left_percent = ((span.timestamp - min_start_time).to_f / total_duration * 100) %>
16
54
  <% width_percent = [((span.end_time - span.timestamp).to_f / total_duration * 100), 0.1].max %>
17
55
  <% right_percent = (100 - left_percent - width_percent) %>
18
56
 
19
- <div style="display: flex; height: 1.5em;">
20
- <div style="flex: <%= left_percent %>"></div>
21
- <div style="flex: <%= width_percent %>; display: flex; align-items: center; justify-content: center; border-radius: 5px" class="has-background-primary-15">
57
+ <div class="span-row" style="width: 100%; height: 6.5em;">
58
+ <div style="left: 0; width: 100%">
59
+ <div class="py-3" style="display: block; position: relative;">
60
+ <div class="span-bar" style="left: <%= left_percent %>%; width: <%= width_percent %>%; min-width: 0.5em; height: 24px; border-radius: 5px; background-color: <%= span_type_color(span) %>"></div>
61
+ <div class="span-details mt-1 mb-2" style="min-width: <%= width_percent + right_percent %>%;">
62
+ <div class="is-flex is-align-items-center">
63
+ <span class="has-text-grey-lighter truncate"><%= link_to span.name, span_path(span.uuid) %></span>
64
+ <span class="has-text-grey ml-2" style="white-space: nowrap;"><%= span.duration.round(2) %> ms</span>
65
+ </div>
66
+ <div class="summary has-text-grey"><%= span.summary %></div>
67
+ </div>
68
+ </div>
22
69
  </div>
23
- <div style="flex: <%= right_percent %>;"></div>
24
- </div>
25
- <div style="margin-left: <%= left_percent %>%" class="mt-1 mb-2">
26
- <p>
27
- <span class="has-text-grey-lighter "><%= span.name %></span><span class="has-text-grey pl-2"><%= span.duration.round(2) %>ms</span>
28
- </p>
29
- <p>
30
- <%= span.summary %>
31
- </p>
32
70
  </div>
33
71
  <% end %>
34
- </div>
72
+ </div>
data/config/routes.rb CHANGED
@@ -3,4 +3,5 @@ SolidApm::Engine.routes.draw do
3
3
 
4
4
  get 'transactions', to: 'transactions#index'
5
5
  get 'transactions/:uuid/spans', to: 'transactions#spans', as: 'transaction_spans'
6
+ get 'spans/:uuid', to: 'spans#show', as: 'span'
6
7
  end
@@ -1,3 +1,3 @@
1
1
  module SolidApm
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.1"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Francis Bastien
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: actionpack
@@ -121,6 +121,7 @@ files:
121
121
  - app/assets/javascripts/solid_apm/application.js
122
122
  - app/assets/stylesheets/solid_apm/application.css
123
123
  - app/controllers/solid_apm/application_controller.rb
124
+ - app/controllers/solid_apm/spans_controller.rb
124
125
  - app/controllers/solid_apm/transactions_controller.rb
125
126
  - app/helpers/solid_apm/application_helper.rb
126
127
  - app/jobs/solid_apm/application_job.rb
@@ -136,7 +137,7 @@ files:
136
137
  - app/views/javascripts/_javascripts.html.erb
137
138
  - app/views/layouts/solid_apm/application.html.erb
138
139
  - app/views/solid_apm/application/_time_range_form.html.erb
139
- - app/views/solid_apm/spans/index.html.erb
140
+ - app/views/solid_apm/spans/show.html.erb
140
141
  - app/views/solid_apm/transactions/_charts.html.erb
141
142
  - app/views/solid_apm/transactions/index.html.erb
142
143
  - app/views/solid_apm/transactions/spans.html.erb
@@ -169,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
170
  - !ruby/object:Gem::Version
170
171
  version: '0'
171
172
  requirements: []
172
- rubygems_version: 3.6.2
173
+ rubygems_version: 3.6.7
173
174
  specification_version: 4
174
175
  summary: SolidApm is a DB base engine for Application Performance Monitoring.
175
176
  test_files: []
@@ -1,22 +0,0 @@
1
- <div data-controller="spans-chart" data-spans-chart-id-value="<%= params[:id] %>"></div>
2
-
3
- <table class="table">
4
- <thead>
5
- <tr>
6
- <% SolidApm::Span.attribute_names.each do |attribute| %>
7
- <% next if attribute.to_s.end_with?('_at') %>
8
- <th scope="col"><%= attribute.humanize %></th>
9
- <% end %>
10
- </tr>
11
- </thead>
12
- <tbody>
13
- <% spans.each do |span| %>
14
- <tr>
15
- <% span.attributes.each do |attribute| %>
16
- <% next if attribute[0].to_s.end_with?('_at') %>
17
- <td><%= attribute[1] %></td>
18
- <% end %>
19
- </tr>
20
- <% end %>
21
- </tbody>
22
- </table>