rails_observatory 0.1.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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/rails_observatory_manifest.js +2 -0
  5. data/app/assets/images/rails_observatory/logo.svg +8 -0
  6. data/app/assets/js/application.js +88 -0
  7. data/app/assets/js/controllers/chart_controller.js +176 -0
  8. data/app/assets/js/controllers/event_details_controller.js +15 -0
  9. data/app/assets/js/controllers/index.js +9 -0
  10. data/app/assets/js/controllers/sparkline_controller.js +72 -0
  11. data/app/assets/stylesheets/application/card.css +51 -0
  12. data/app/assets/stylesheets/application/chart.css +34 -0
  13. data/app/assets/stylesheets/application/dropdown.css +62 -0
  14. data/app/assets/stylesheets/application/global_modifiers.css +10 -0
  15. data/app/assets/stylesheets/application/query_table.css +68 -0
  16. data/app/assets/stylesheets/application/side_nav.css +62 -0
  17. data/app/assets/stylesheets/application/side_panel.css +35 -0
  18. data/app/assets/stylesheets/application/tab_nav.css +64 -0
  19. data/app/assets/stylesheets/application/table_chart.css +66 -0
  20. data/app/assets/stylesheets/application/tbd.css +70 -0
  21. data/app/assets/stylesheets/application/top_nav.css +33 -0
  22. data/app/assets/stylesheets/application.css +42 -0
  23. data/app/assets/stylesheets/elements/a.css +8 -0
  24. data/app/assets/stylesheets/elements/button.css +21 -0
  25. data/app/assets/stylesheets/elements/details.css +12 -0
  26. data/app/assets/stylesheets/elements/root.css +26 -0
  27. data/app/assets/stylesheets/elements/section.css +9 -0
  28. data/app/assets/stylesheets/errors/show/details.css +13 -0
  29. data/app/assets/stylesheets/layout/app.css +23 -0
  30. data/app/assets/stylesheets/layout/details-side-panel.css +15 -0
  31. data/app/assets/stylesheets/layout/requests.css +45 -0
  32. data/app/assets/stylesheets/layout/two-column.css +17 -0
  33. data/app/assets/stylesheets/mixins/nav_button.css +19 -0
  34. data/app/assets/stylesheets/requests/stats.css +35 -0
  35. data/app/controllers/rails_observatory/application_controller.rb +24 -0
  36. data/app/controllers/rails_observatory/errors_controller.rb +27 -0
  37. data/app/controllers/rails_observatory/jobs_controller.rb +25 -0
  38. data/app/controllers/rails_observatory/mailers_controller.rb +11 -0
  39. data/app/controllers/rails_observatory/requests_controller.rb +33 -0
  40. data/app/helpers/rails_observatory/application_helper.rb +110 -0
  41. data/app/jobs/rails_observatory/application_job.rb +4 -0
  42. data/app/mailers/rails_observatory/application_mailer.rb +6 -0
  43. data/app/views/layouts/rails_observatory/application.html.erb +93 -0
  44. data/app/views/new_user_mailer/greeting.html.erb +1 -0
  45. data/app/views/posts/index.html.erb +1 -0
  46. data/app/views/rails_observatory/application/_chart.html.erb +23 -0
  47. data/app/views/rails_observatory/application/_events_table.html.erb +24 -0
  48. data/app/views/rails_observatory/application/_sparkline.html.erb +17 -0
  49. data/app/views/rails_observatory/application/_trace.html.erb +122 -0
  50. data/app/views/rails_observatory/errors/index.html.erb +87 -0
  51. data/app/views/rails_observatory/errors/show.html.erb +193 -0
  52. data/app/views/rails_observatory/jobs/_table_chart.html.erb +29 -0
  53. data/app/views/rails_observatory/jobs/index.html.erb +20 -0
  54. data/app/views/rails_observatory/jobs/show.html.erb +8 -0
  55. data/app/views/rails_observatory/logs/index.html.erb +18 -0
  56. data/app/views/rails_observatory/mailers/index.html.erb +11 -0
  57. data/app/views/rails_observatory/mailers/show.html.erb +10 -0
  58. data/app/views/rails_observatory/requests/_text_gauge.html.erb +4 -0
  59. data/app/views/rails_observatory/requests/index.html.erb +56 -0
  60. data/app/views/rails_observatory/requests/show.html.erb +16 -0
  61. data/config/routes.rb +7 -0
  62. data/lib/rails_observatory/action_mailer_subscriber.rb +14 -0
  63. data/lib/rails_observatory/engine.rb +49 -0
  64. data/lib/rails_observatory/event_collector.rb +43 -0
  65. data/lib/rails_observatory/log_collector.rb +46 -0
  66. data/lib/rails_observatory/mailer_previews/delivered_mail_preview.rb +9 -0
  67. data/lib/rails_observatory/middleware.rb +77 -0
  68. data/lib/rails_observatory/models/error.rb +67 -0
  69. data/lib/rails_observatory/models/event_collection.rb +137 -0
  70. data/lib/rails_observatory/models/events.rb +22 -0
  71. data/lib/rails_observatory/models/job_trace.rb +28 -0
  72. data/lib/rails_observatory/models/logs.rb +9 -0
  73. data/lib/rails_observatory/models/mail_delivery.rb +33 -0
  74. data/lib/rails_observatory/models/redis_model.rb +112 -0
  75. data/lib/rails_observatory/models/request_trace.rb +29 -0
  76. data/lib/rails_observatory/railties/active_job_instrumentation.rb +48 -0
  77. data/lib/rails_observatory/railties/redis_runtime.rb +11 -0
  78. data/lib/rails_observatory/redis/logging_middleware.rb +22 -0
  79. data/lib/rails_observatory/redis/redis_client_instrumentation.rb +18 -0
  80. data/lib/rails_observatory/redis/time_series/increment_script.lua +67 -0
  81. data/lib/rails_observatory/redis/time_series/insertion.rb +73 -0
  82. data/lib/rails_observatory/redis/time_series/query_builder.rb +149 -0
  83. data/lib/rails_observatory/redis/time_series/timing_script.lua +89 -0
  84. data/lib/rails_observatory/redis/time_series.rb +91 -0
  85. data/lib/rails_observatory/serializers/event_serializer.rb +19 -0
  86. data/lib/rails_observatory/serializers/headers_serializer.rb +12 -0
  87. data/lib/rails_observatory/serializers/job_serializer.rb +11 -0
  88. data/lib/rails_observatory/serializers/mail_delivery_job_serializer.rb +14 -0
  89. data/lib/rails_observatory/serializers/request_serializer.rb +17 -0
  90. data/lib/rails_observatory/serializers/response_serializer.rb +14 -0
  91. data/lib/rails_observatory/serializers/serializer.rb +51 -0
  92. data/lib/rails_observatory/version.rb +3 -0
  93. data/lib/rails_observatory.rb +3 -0
  94. data/public/assets/js/application.js +11186 -0
  95. data/public/assets/logo_with_text.svg +21 -0
  96. data/public/assets/stylesheets/application.css +757 -0
  97. metadata +197 -0
@@ -0,0 +1,25 @@
1
+ module RailsObservatory
2
+ class JobsController < ApplicationController
3
+
4
+ before_action :set_duration
5
+
6
+ around_action :set_time_range
7
+
8
+ def index
9
+ JobTrace.ensure_index
10
+ @recent_jobs = JobTrace.all.take(10)
11
+ end
12
+
13
+ def show
14
+ @job = JobTrace.find(params[:id])
15
+ @events = @job.events
16
+
17
+ end
18
+
19
+ def set_time_range
20
+ TimeSeries.with_slice(duration.seconds.ago..) do
21
+ yield
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module RailsObservatory
2
+ class MailersController < ApplicationController
3
+
4
+ def index
5
+
6
+ MailDelivery.ensure_index
7
+ @deliveries = MailDelivery.all
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ module RailsObservatory
2
+ class RequestsController < ApplicationController
3
+
4
+ def index
5
+ @time_range = (duration.seconds.ago..)
6
+
7
+ if params[:controller_action].blank?
8
+ @count_by_controller = TimeSeries.where(name: 'request.count', action: '*')
9
+ .slice(@time_range)
10
+ .downsample(1, using: :sum)
11
+ .select { _1.value > 0 }
12
+ .sort_by(&:value)
13
+ .reverse
14
+
15
+ @latency_by_controller = TimeSeries.where(name: 'request.latency', action: '*')
16
+ .slice(@time_range)
17
+ .downsample(1, using: :avg)
18
+ .index_by { _1.labels[:action] }
19
+ end
20
+
21
+ RequestTrace.ensure_index
22
+ @events = RequestTrace.all
23
+ end
24
+
25
+ def show
26
+ @request = RequestTrace.find(params[:id])
27
+ @middleware_events = @request.events.only('process_middleware.action_dispatch')
28
+ @events = @request.events
29
+ @icicle_chart_series = @events.to_series
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,110 @@
1
+ require 'rouge'
2
+ module RailsObservatory
3
+ module ApplicationHelper
4
+
5
+ def highlight_source_extract(source_extract)
6
+ source_extract.symbolize_keys => {code:, line_number:}
7
+ fmt = Rouge::Formatters::HTMLLineTable.new(Rouge::Formatters::HTML.new, start_line: code.keys.first.to_s.to_i)
8
+ html = Nokogiri::HTML4(Rouge.highlight(code.values.flatten.join(""), 'ruby', fmt))
9
+ html.css("#line-#{line_number}").add_class('hll')
10
+ line = code[line_number.to_s]
11
+ if line.length > 1
12
+ html.css("#line-#{line_number}").attr('data-highlight-start', line[0].length).attr('data-highlight-length', line[1].length)
13
+ end
14
+ html.to_html
15
+ end
16
+
17
+ def series_for(name:, aggregate_using:, time_range: nil, downsample: 120, **opts)
18
+ series = RailsObservatory::TimeSeries.where(name:, **opts).downsample(downsample, using: aggregate_using)
19
+ series = series.slice(time_range) if time_range
20
+ series
21
+ end
22
+
23
+ def series_value(name:, aggregate_using:)
24
+ series = series_for(name:, aggregate_using: aggregate_using, downsample: 1)
25
+ series.first&.value
26
+ end
27
+
28
+ def time_slice_start
29
+ @time_range.begin.to_i * 1000
30
+ end
31
+
32
+ def time_slice_end
33
+ time = @time_range.end.nil? ? Time.now.to_i : @time_range.end.to_i
34
+ time * 1000
35
+ end
36
+
37
+ def format_event_value(value)
38
+ if value.is_a?(Numeric)
39
+ value.round(2)
40
+ else
41
+ value
42
+ end
43
+ end
44
+
45
+ def pretty_backtrace_location(backtrace_line)
46
+ path, lineno, method = backtrace_line.split(':')
47
+ path = path.sub(Gem.paths.home, '')
48
+ # remove 'in ' from method name
49
+ method = method.match(/in `([^']+) (:?in .*)'/)
50
+ method = method[1] if method
51
+ tag.div(class: 'backtrace-line') do
52
+ tag.span(path, class: '_path') +
53
+ ' in ' +
54
+ tag.span(method, class: '_method') +
55
+ ' at line ' +
56
+ tag.span(lineno, class: '_lineno')
57
+
58
+ end
59
+ end
60
+
61
+ def preview_mail_path(message_id)
62
+ "/rails/mailers/delivered_mail/preview?message_id=#{message_id}"
63
+ end
64
+
65
+ # {"used_memory"=>"46573752",
66
+ # "used_memory_human"=>"44.42M",
67
+ # "used_memory_rss"=>"70778880",
68
+ # "used_memory_rss_human"=>"67.50M",
69
+ # "used_memory_peak"=>"49028032",
70
+ # "used_memory_peak_human"=>"46.76M",
71
+ # "used_memory_peak_perc"=>"94.99%",
72
+ # "used_memory_overhead"=>"2195208",
73
+ # "used_memory_startup"=>"1118008",
74
+ # "used_memory_dataset"=>"44378544",
75
+ # "used_memory_dataset_perc"=>"97.63%",
76
+ # "allocator_allocated"=>"46540912",
77
+ # "allocator_active"=>"70693888",
78
+ # "allocator_resident"=>"70693888",
79
+ # "total_system_memory"=>"8225423360",
80
+ # "total_system_memory_human"=>"7.66G",
81
+ # "used_memory_lua"=>"84992",
82
+ # "used_memory_lua_human"=>"83.00K",
83
+ # "used_memory_scripts"=>"4768",
84
+ # "used_memory_scripts_human"=>"4.66K",
85
+ # "number_of_cached_scripts"=>"2",
86
+ # "maxmemory"=>"0",
87
+ # "maxmemory_human"=>"0B",
88
+ # "maxmemory_policy"=>"noeviction",
89
+ # "allocator_frag_ratio"=>"1.52",
90
+ # "allocator_frag_bytes"=>"24152976",
91
+ # "allocator_rss_ratio"=>"1.00",
92
+ # "allocator_rss_bytes"=>"0",
93
+ # "rss_overhead_ratio"=>"1.00",
94
+ # "rss_overhead_bytes"=>"84992",
95
+ # "mem_fragmentation_ratio"=>"1.52",
96
+ # "mem_fragmentation_bytes"=>"24237968",
97
+ # "mem_not_counted_for_evict"=>"0",
98
+ # "mem_replication_backlog"=>"0",
99
+ # "mem_clients_slaves"=>"0",
100
+ # "mem_clients_normal"=>"51176",
101
+ # "mem_aof_buffer"=>"0",
102
+ # "mem_allocator"=>"libc",
103
+ # "active_defrag_running"=>"0",
104
+ # "lazyfree_pending_objects"=>"0",
105
+ # "lazyfreed_objects"=>"0"}
106
+ def redis_mem_info
107
+ @info ||= Rails.configuration.rails_observatory.redis.call('info', 'memory').split("\r\n").slice(1..).map { _1.split(":") }.to_h
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,4 @@
1
+ module RailsObservatory
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module RailsObservatory
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,93 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Rails Observatory</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <link rel="preload" href="<%= "#{root_path}assets/stylesheets/application.css" %>" as="style">
8
+ <link rel="stylesheet" href="<%= "#{root_path}assets/stylesheets/application.css" %>">
9
+ <script type="text/javascript" src="<%= "#{root_path}assets/js/application.js" %>"></script>
10
+
11
+ <style>
12
+ <%= Rouge::Theme.find('github.dark').render %>
13
+ </style>
14
+ </head>
15
+ <body class="layout-app">
16
+ <nav class="side-nav layout-app-side-nav">
17
+ <img height="36" src="<%= "#{root_path}assets/logo_with_text.svg" %>" alt="RailsObservatory">
18
+ <ul>
19
+ <li><%= link_to root_path, class: "#{'_active' if request.path == root_path || request.path =~ /requests/}" do %>
20
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24">
21
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" />
22
+ </svg>
23
+ Requests
24
+ <% end %></li>
25
+ <li><%= link_to jobs_path, class: "#{'_active' if request.path =~ /jobs/}" do %>
26
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24">
27
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008z" />
28
+ </svg>
29
+ Jobs
30
+ <% end %></li>
31
+ <li><%= link_to mailers_path, class: "#{'_active' if request.path =~ /mailers/}" do %>
32
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24">
33
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
34
+ </svg>
35
+
36
+ Mailers
37
+ <% end %></li>
38
+ <li><%= link_to errors_path, class: "#{'_active' if request.path =~ /errors/}" do %>
39
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24">
40
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 12.75c1.148 0 2.278.08 3.383.237 1.037.146 1.866.966 1.866 2.013 0 3.728-2.35 6.75-5.25 6.75S6.75 18.728 6.75 15c0-1.046.83-1.867 1.866-2.013A24.204 24.204 0 0112 12.75zm0 0c2.883 0 5.647.508 8.207 1.44a23.91 23.91 0 01-1.152 6.06M12 12.75c-2.883 0-5.647.508-8.208 1.44.125 2.104.52 4.136 1.153 6.06M12 12.75a2.25 2.25 0 002.248-2.354M12 12.75a2.25 2.25 0 01-2.248-2.354M12 8.25c.995 0 1.971-.08 2.922-.236.403-.066.74-.358.795-.762a3.778 3.778 0 00-.399-2.25M12 8.25c-.995 0-1.97-.08-2.922-.236-.402-.066-.74-.358-.795-.762a3.734 3.734 0 01.4-2.253M12 8.25a2.25 2.25 0 00-2.248 2.146M12 8.25a2.25 2.25 0 012.248 2.146M8.683 5a6.032 6.032 0 01-1.155-1.002c.07-.63.27-1.222.574-1.747m.581 2.749A3.75 3.75 0 0115.318 5m0 0c.427-.283.815-.62 1.155-.999a4.471 4.471 0 00-.575-1.752M4.921 6a24.048 24.048 0 00-.392 3.314c1.668.546 3.416.914 5.223 1.082M19.08 6c.205 1.08.337 2.187.392 3.314a23.882 23.882 0 01-5.223 1.082" />
41
+ </svg>
42
+ Errors
43
+ <% end %></li>
44
+ </ul>
45
+ <div class="_redis-stats">
46
+ <strong>Redis</strong> <%= redis_mem_info['used_memory_human'] %> /
47
+ <%= redis_mem_info['used_memory_rss_human'] %>
48
+ </div>
49
+ </nav>
50
+ <nav class="top-nav">
51
+ <span>
52
+ <h1>
53
+ <span class="_subtitle">
54
+ <% if content_for? :subtitle %><%= yield :subtitle %>
55
+ <% end %>
56
+ </span>
57
+ <%= yield :title %>
58
+ </h1>
59
+ </span>
60
+ <%= yield :top_nav %>
61
+ <% unless content_for?(:hide_duration) %>
62
+ <div class="dropdown">
63
+ <button class="secondary">
64
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" height="20">
65
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
66
+ </svg>
67
+ <span>Past <%= duration.inspect %></span>
68
+ <svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" height="20">
69
+ <path fill-rule="evenodd" d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" clip-rule="evenodd" />
70
+ </svg>
71
+ </button>
72
+
73
+ <ul>
74
+ <% [5.minutes, 10.minutes, 15.minutes, 30.minutes, 1.hour, 4.hours, 1.day, 2.days, 7.days, 1.month].each do |time_frame| %>
75
+ <li>
76
+ <a href="?duration=<%= time_frame %>" class="<%= 'active' if time_frame == duration %>">
77
+ <span>Past <%= time_frame.inspect %></span>
78
+ <svg height="18" viewBox="0 0 20 20" fill="currentColor">
79
+ <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd" />
80
+ </svg>
81
+ </a>
82
+ </li>
83
+ <% end %>
84
+
85
+ </ul>
86
+ </div>
87
+ <% end %>
88
+ </nav>
89
+ <main class="<%= yield :main_css_class %>">
90
+ <%= yield %>
91
+ </main>
92
+ </body>
93
+ </html>
@@ -0,0 +1 @@
1
+ Greetings!
@@ -0,0 +1 @@
1
+ Posts
@@ -0,0 +1,23 @@
1
+ <% series = local_assigns[:series] %>
2
+
3
+ <div id="<%= rand(100) %>"
4
+ class="chart"
5
+ data-controller="chart"
6
+ data-chart-name="<%= local_assigns[:name] %>"
7
+ data-chart-type-value="<%= local_assigns[:type] %>"
8
+ <% unless local_assigns[:autobound] %>
9
+ data-chart-start-x-value="<%= series.first&.start_time_ms %>"
10
+ data-chart-end-x-value="<%= series.first&.end_time_ms %>"
11
+ <% end %>
12
+ >
13
+ <% if local_assigns[:series] %>
14
+ <div data-chart-target="chart"></div>
15
+ <% local_assigns[:series].each do |series| %>
16
+ <% name = series.respond_to?(:name) ? series.name : series[:name] %>
17
+ <% data = series.respond_to?(:filled_data) ? series.filled_data : series[:data] %>
18
+ <script type="application/json" data-chart-target="data" data-series-name="<%= name.split('/').last %>">
19
+ <%== data.to_json %>
20
+ </script>
21
+ <% end %>
22
+ <% end %>
23
+ </div>
@@ -0,0 +1,24 @@
1
+ <div class="query-table --scrollable" style="--column-count: <%= fields.size %>">
2
+ <header>
3
+ <% fields.each do |f| %>
4
+ <div class="query-table-column"><%= f.to_s.humanize(capitalize: true, keep_id_suffix: true) %></div>
5
+ <% end %>
6
+ </header>
7
+ <div class="query-table-body --scrollable">
8
+ <% events.each do |e| %>
9
+ <div class="query-table-row">
10
+ <% fields.each do |f| %>
11
+ <% if (formatter = local_assigns[:formatters]&.fetch(f, nil)) %>
12
+ <% if formatter.arity == 2 %>
13
+ <div class="query-table-column"><%= formatter.call(e.respond_to?(f) ? e.send(f) : 'unknownattribute', e) %></div>
14
+ <% else %>
15
+ <div class="query-table-column"><%= formatter.call(e.respond_to?(f) ? e.send(f) : 'unknownattribute') %></div>
16
+ <% end %>
17
+ <% else %>
18
+ <div class="query-table-column"><%= format_event_value(e.respond_to?(f) ? e.send(f) : 'unknownattribute') %></div>
19
+ <% end %>
20
+ <% end %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ </div>
@@ -0,0 +1,17 @@
1
+ <% series = Array.wrap(local_assigns[:series]) %>
2
+ <div id="<%= rand(100) %>"
3
+ class="_chart"
4
+ data-controller="sparkline"
5
+ data-sparkline-name="<%= local_assigns[:name] %>"
6
+ data-sparkline-type-value="<%= local_assigns[:type] %>"
7
+ data-sparkline-start-x-value="<%= series.first.start_time.to_i.in_milliseconds %>"
8
+ data-sparkline-end-x-value="<%= series.first.end_time.to_i.in_milliseconds %>">
9
+ <% if series.any? %>
10
+ <div data-sparkline-target="chart"></div>
11
+ <% series.each do |series| %>
12
+ <script type="application/json" data-sparkline-target="data" data-series-name="<%= series.name.split('/').last %>">
13
+ <%== series.filled_data.map { |(ts, v)| v.to_i == 0 ? [ts, 0] : [ts,v.to_i]}.to_json %>
14
+ </script>
15
+ <% end %>
16
+ <% end %>
17
+ </div>
@@ -0,0 +1,122 @@
1
+ <%= render 'chart', type: 'icicle', series: @events.to_series, autobound: true %>
2
+
3
+ <div class="tabs layout-events-breakdown-details">
4
+ <nav class="tabs-nav --sticky">
5
+ <label class="tabs-button">
6
+ Event Details
7
+ <input type="radio" name="tabs" checked>
8
+ </label>
9
+ <label class="tabs-button">
10
+ Logs
11
+ <input type="radio" name="tabs">
12
+ </label>
13
+ <label class="tabs-button">
14
+ Mail
15
+ <% if model.mail_events.size > 0 %>
16
+ <span style="color: var(--black-secondary)"><%= model.mail_events.size %></span>
17
+ <% end %>
18
+ <input type="radio" name="tabs">
19
+ </label>
20
+ <label class="tabs-button">
21
+ Jobs
22
+ <input type="radio" name="tabs">
23
+ </label>
24
+ <label class="tabs-button">
25
+ Errors
26
+ <input type="radio" name="tabs">
27
+ </label>
28
+ </nav>
29
+ <div class="tab-content event-details" data-controller="event-details" data-action="chart:selected@window->event-details#show">
30
+ <% @events.each_with_index do |e, index| %>
31
+ <div class="event-detail" id="<%= e['start_at'] %>" <%= 'hidden' unless index.zero? %> style="padding-inline: 2rem;">
32
+ <div>
33
+ <h3><%= e['name'] %> (<%= e['self_time'].to_f.round(2) %>ms)</h3>
34
+ <span style="color: var(--black-secondary)">Event spanned from <%= e['relative_start_at'].round(2) %>ms
35
+ to <%= e['relative_end_at'].round(2) %>ms
36
+ with a total duration of <%= e['duration'].round(2) %>ms
37
+ </span>
38
+ </div>
39
+ <% if e['middleware_stack'].present? %>
40
+ <div class="table-bar-chart">
41
+ <div class="table-bar-header">Middleware Stack</div>
42
+ <% total_time = e['self_time'] %>
43
+
44
+ <% e['middleware_stack'].each do |middleware_event| %>
45
+ <div class="table-bar-row">
46
+ <% percent = middleware_event['self_time'] / total_time * 100 %>
47
+ <div class="bar" style="width: <%= percent.round(1) %>%"></div>
48
+ <%= middleware_event['payload']['middleware'] %>
49
+ <div> <%= middleware_event['self_time'].to_f.round(2) %>ms</div>
50
+ <div style="color: var(--black-secondary);white-space: nowrap">(<%= percent.round(1) %>% of total)</div>
51
+ </div>
52
+ <% end %>
53
+ </div>
54
+ <% else %>
55
+ <dl style="">
56
+ <dt>Payload</dt>
57
+ <% if e['payload'].empty? %>
58
+ <dd>Nothing in payload</dd>
59
+ <% else %>
60
+ <dd class="highlight">
61
+ <pre class="--scrollable"><%== Rouge.highlight(e['payload'].to_yaml, 'yaml', Rouge::Formatters::HTMLLinewise.new(Rouge::Formatters::HTML.new, class: 'line')) %></pre>
62
+ <!-- <pre style="width:100%;overflow: auto;"><%#== Rouge.highlight(JSON.pretty_generate(e['payload']) , 'json', 'html') %></pre>-->
63
+ </dd>
64
+ <% end %>
65
+
66
+ </dl>
67
+ <% end %>
68
+
69
+ </div>
70
+ <% end %>
71
+ </div>
72
+ <div class="tab-content logs">
73
+ <log-lines style="display:grid;grid-template-columns: min-content min-content 1fr; gap:1rem;padding-block: 1rem;">
74
+ <% model.logs.each do |log| %>
75
+ <log-line style="display: grid; padding-inline: 2rem; grid-template-columns: subgrid;grid-column: 1/-1;">
76
+ <time style="white-space: nowrap"><%= Time.at(log['time']) %></time>
77
+ <log-level><%= log['severity'] %></log-level>
78
+ <log-message><%= log['message'] %></log-message>
79
+ </log-line>
80
+ <% end %>
81
+ </log-lines>
82
+ </div>
83
+ <div class="tab-content mail">
84
+ <simple-list class="simple-list">
85
+ <% if model.mail_events.empty? %>
86
+ <li>No mail enqueued or delivered</li>
87
+ <% end %>
88
+ <% model.mail_events.each do |e| %>
89
+ <li>
90
+ Mail Delivery <%= e.dig('payload', 'mailer') %>
91
+ <div>To: <%= e.dig('payload', 'to') %></div>
92
+ <div>From: <%= e.dig('payload', 'from') %></div>
93
+ <div>Subject: <%= e.dig('payload', 'subject') %></div>
94
+ <a href="<%= preview_mail_path(e.dig('payload', 'message_id')) %>" target="_blank">View Email</a>
95
+ </li>
96
+ <% end %>
97
+ </simple-list>
98
+ </div>
99
+ <div class="tab-content jobs">
100
+ <ul class="simple-list">
101
+ <% if model.events.only('enqueue.active_job').to_a.empty? %>
102
+ <li>No jobs were enqueued</li>
103
+ <% end %>
104
+ <% model.events.only('enqueue.active_job').each do |event| %>
105
+ <li>
106
+ <div class="simple-list-title"><%= event.dig('payload', 'job', 'class') %></div>
107
+ <div>
108
+ Enqueued job (
109
+ <span><%= link_to event.dig('payload', 'job', 'job_id').slice(0...8), job_path(event.dig('payload', 'job', 'job_id')) %></span>
110
+ ) to
111
+ <span><%= event.dig('payload', 'job', 'queue_name') %></span>
112
+ queue at
113
+ <span><%= event['start_at'] %></span>
114
+ </div>
115
+ </li>
116
+ <% end %>
117
+ </ul>
118
+ </div>
119
+ <div class="tab-content errors">
120
+ errors
121
+ </div>
122
+ </div>
@@ -0,0 +1,87 @@
1
+ <% content_for(:title, 'Errors') %>
2
+ <% content_for(:hide_duration, 'true') %>
3
+
4
+
5
+ <style>
6
+ @scope {
7
+ :scope {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 0rem;
11
+ padding: 0;
12
+ }
13
+ }
14
+
15
+ .error {
16
+ padding: 1.5rem 2rem;
17
+ border-bottom: 1px solid var(--divider);
18
+ display: grid;
19
+ grid-template-areas:
20
+ 'name-and-location graph count'
21
+ 'message graph count'
22
+ 'time graph count';
23
+ grid-template-columns: minmax(14rem, 1fr) 200px minmax(min-content, 4rem);
24
+ column-gap: 1rem;
25
+ row-gap: .3rem;
26
+
27
+ ._name-and-location {
28
+ display: flex;
29
+ gap: 1rem;
30
+ align-items: baseline;
31
+ overflow: hidden;
32
+
33
+
34
+ & > :first-child {
35
+ font-size: 1.25rem;
36
+ font-weight: 400;
37
+ }
38
+
39
+ & > :last-child {
40
+ color: var(--black-secondary);
41
+ text-overflow: ellipsis;
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ }
45
+ }
46
+
47
+ ._message {
48
+ grid-area: message;
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ overflow: hidden;
52
+ }
53
+
54
+ ._count {
55
+ grid-area: count;
56
+ text-align: center;
57
+ font-size: 1.5rem;
58
+ font-weight: 400;
59
+ align-self: center;
60
+ }
61
+
62
+ ._graph {
63
+ grid-area: graph;
64
+ text-align: right;
65
+ align-items: center;
66
+ }
67
+
68
+ }
69
+ </style>
70
+
71
+ <% @errors.each do |e| %>
72
+ <div class="error">
73
+ <div class="_name-and-location">
74
+ <a href="<%= error_path(e.fingerprint) %>"><%= e.class_name %></a>
75
+ <span><%= e.location.classify %></span>
76
+ </div>
77
+ <div class="_message"><%= e.message %></div>
78
+ <div class="_time"><%= time_ago_in_words(Time.at(e.time)) %> ago</div>
79
+
80
+ <div class="_graph">
81
+ <% if @series_by_fingerprint[e.fingerprint] %>
82
+ <%= render "sparkline", type: "bar", series: @series_by_fingerprint[e.fingerprint] %>
83
+ <% end %>
84
+ </div>
85
+ <div class="_count"><%= @count_by_fingerprint[e.fingerprint] %><%# e.count %></div>
86
+ </div>
87
+ <% end %>