rails_observatory 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +42 -0
- data/Rakefile +8 -0
- data/app/assets/config/rails_observatory_manifest.js +2 -0
- data/app/assets/images/rails_observatory/logo.svg +8 -0
- data/app/assets/js/application.js +88 -0
- data/app/assets/js/controllers/chart_controller.js +176 -0
- data/app/assets/js/controllers/event_details_controller.js +15 -0
- data/app/assets/js/controllers/index.js +9 -0
- data/app/assets/js/controllers/sparkline_controller.js +72 -0
- data/app/assets/stylesheets/application/card.css +51 -0
- data/app/assets/stylesheets/application/chart.css +34 -0
- data/app/assets/stylesheets/application/dropdown.css +62 -0
- data/app/assets/stylesheets/application/global_modifiers.css +10 -0
- data/app/assets/stylesheets/application/query_table.css +68 -0
- data/app/assets/stylesheets/application/side_nav.css +62 -0
- data/app/assets/stylesheets/application/side_panel.css +35 -0
- data/app/assets/stylesheets/application/tab_nav.css +64 -0
- data/app/assets/stylesheets/application/table_chart.css +66 -0
- data/app/assets/stylesheets/application/tbd.css +70 -0
- data/app/assets/stylesheets/application/top_nav.css +33 -0
- data/app/assets/stylesheets/application.css +42 -0
- data/app/assets/stylesheets/elements/a.css +8 -0
- data/app/assets/stylesheets/elements/button.css +21 -0
- data/app/assets/stylesheets/elements/details.css +12 -0
- data/app/assets/stylesheets/elements/root.css +26 -0
- data/app/assets/stylesheets/elements/section.css +9 -0
- data/app/assets/stylesheets/errors/show/details.css +13 -0
- data/app/assets/stylesheets/layout/app.css +23 -0
- data/app/assets/stylesheets/layout/details-side-panel.css +15 -0
- data/app/assets/stylesheets/layout/requests.css +45 -0
- data/app/assets/stylesheets/layout/two-column.css +17 -0
- data/app/assets/stylesheets/mixins/nav_button.css +19 -0
- data/app/assets/stylesheets/requests/stats.css +35 -0
- data/app/controllers/rails_observatory/application_controller.rb +24 -0
- data/app/controllers/rails_observatory/errors_controller.rb +27 -0
- data/app/controllers/rails_observatory/jobs_controller.rb +25 -0
- data/app/controllers/rails_observatory/mailers_controller.rb +11 -0
- data/app/controllers/rails_observatory/requests_controller.rb +33 -0
- data/app/helpers/rails_observatory/application_helper.rb +110 -0
- data/app/jobs/rails_observatory/application_job.rb +4 -0
- data/app/mailers/rails_observatory/application_mailer.rb +6 -0
- data/app/views/layouts/rails_observatory/application.html.erb +93 -0
- data/app/views/new_user_mailer/greeting.html.erb +1 -0
- data/app/views/posts/index.html.erb +1 -0
- data/app/views/rails_observatory/application/_chart.html.erb +23 -0
- data/app/views/rails_observatory/application/_events_table.html.erb +24 -0
- data/app/views/rails_observatory/application/_sparkline.html.erb +17 -0
- data/app/views/rails_observatory/application/_trace.html.erb +122 -0
- data/app/views/rails_observatory/errors/index.html.erb +87 -0
- data/app/views/rails_observatory/errors/show.html.erb +193 -0
- data/app/views/rails_observatory/jobs/_table_chart.html.erb +29 -0
- data/app/views/rails_observatory/jobs/index.html.erb +20 -0
- data/app/views/rails_observatory/jobs/show.html.erb +8 -0
- data/app/views/rails_observatory/logs/index.html.erb +18 -0
- data/app/views/rails_observatory/mailers/index.html.erb +11 -0
- data/app/views/rails_observatory/mailers/show.html.erb +10 -0
- data/app/views/rails_observatory/requests/_text_gauge.html.erb +4 -0
- data/app/views/rails_observatory/requests/index.html.erb +56 -0
- data/app/views/rails_observatory/requests/show.html.erb +16 -0
- data/config/routes.rb +7 -0
- data/lib/rails_observatory/action_mailer_subscriber.rb +14 -0
- data/lib/rails_observatory/engine.rb +49 -0
- data/lib/rails_observatory/event_collector.rb +43 -0
- data/lib/rails_observatory/log_collector.rb +46 -0
- data/lib/rails_observatory/mailer_previews/delivered_mail_preview.rb +9 -0
- data/lib/rails_observatory/middleware.rb +77 -0
- data/lib/rails_observatory/models/error.rb +67 -0
- data/lib/rails_observatory/models/event_collection.rb +137 -0
- data/lib/rails_observatory/models/events.rb +22 -0
- data/lib/rails_observatory/models/job_trace.rb +28 -0
- data/lib/rails_observatory/models/logs.rb +9 -0
- data/lib/rails_observatory/models/mail_delivery.rb +33 -0
- data/lib/rails_observatory/models/redis_model.rb +112 -0
- data/lib/rails_observatory/models/request_trace.rb +29 -0
- data/lib/rails_observatory/railties/active_job_instrumentation.rb +48 -0
- data/lib/rails_observatory/railties/redis_runtime.rb +11 -0
- data/lib/rails_observatory/redis/logging_middleware.rb +22 -0
- data/lib/rails_observatory/redis/redis_client_instrumentation.rb +18 -0
- data/lib/rails_observatory/redis/time_series/increment_script.lua +67 -0
- data/lib/rails_observatory/redis/time_series/insertion.rb +73 -0
- data/lib/rails_observatory/redis/time_series/query_builder.rb +149 -0
- data/lib/rails_observatory/redis/time_series/timing_script.lua +89 -0
- data/lib/rails_observatory/redis/time_series.rb +91 -0
- data/lib/rails_observatory/serializers/event_serializer.rb +19 -0
- data/lib/rails_observatory/serializers/headers_serializer.rb +12 -0
- data/lib/rails_observatory/serializers/job_serializer.rb +11 -0
- data/lib/rails_observatory/serializers/mail_delivery_job_serializer.rb +14 -0
- data/lib/rails_observatory/serializers/request_serializer.rb +17 -0
- data/lib/rails_observatory/serializers/response_serializer.rb +14 -0
- data/lib/rails_observatory/serializers/serializer.rb +51 -0
- data/lib/rails_observatory/version.rb +3 -0
- data/lib/rails_observatory.rb +3 -0
- data/public/assets/js/application.js +11186 -0
- data/public/assets/logo_with_text.svg +21 -0
- data/public/assets/stylesheets/application.css +757 -0
- 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,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,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 %>
|