-
-
Notifications
You must be signed in to change notification settings - Fork 633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] create a dashboard for investigating React on Rails SSR performance #1636
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# frozen_string_literal: true | ||
|
||
module TraceVisualizer | ||
class TraceVisualizerController < ActionController::Base | ||
def index | ||
log_file_path = "./log1.log" | ||
log_file_content = File.read(log_file_path) | ||
|
||
map_request_id_to_path = {} | ||
map_request_id_to_operation_stack = {} | ||
|
||
log_file_content.each_line do |line| | ||
# get lines like this: | ||
# [04b9a1be-1312-4053-9598-f500a81f0203] Started GET "/server_side_hello_world_hooks" for ::1 at 2024-06-24 12:27:08 +0300 | ||
# it is a request start line. | ||
# Request id is between square brackets, request method is after "Started", request path is in quotes after method name. | ||
if line =~ /\[(\h{8}-\h{4}-\h{4}-\h{4}-\h{12})\] Started (\w+) "(.*)" for/ | ||
request_id = ::Regexp.last_match(1) | ||
path = ::Regexp.last_match(3) | ||
map_request_id_to_path[request_id] = path | ||
map_request_id_to_operation_stack[request_id] = [] | ||
end | ||
|
||
# Each operation logs the following line to logs | ||
# [04b9a1be-1312-4053-9598-f500a81f0203] [ReactOnRailsPro] [operation-start] PID:49996 server_rendering_component_js_code: HelloWorldHooks, 171923395.5230 | ||
# where last number is the timestamp of the operation start | ||
# After finishing the operation it logs the following line | ||
# [04b9a1be-1312-4053-9598-f500a81f0203] [ReactOnRailsPro] PID:49996 server_rendering_component_js_code: HelloWorldHooks, 2.1ms | ||
# We need to extract the request id, operation name and duration of the operation | ||
# Also, we need to extract suboperations | ||
if line =~ /\[(\h{8}-\h{4}-\h{4}-\h{4}-\h{12})\] \[ReactOnRails\] \[operation-start\] PID:\d+ (\w+): (.*), (\d+\.\d+)/ | ||
request_id = ::Regexp.last_match(1) | ||
operation_name = ::Regexp.last_match(2) | ||
message = ::Regexp.last_match(3) | ||
start_time = ::Regexp.last_match(4).to_f | ||
map_request_id_to_operation_stack[request_id] << { | ||
operation_name: operation_name, | ||
message: message, | ||
suboperations: [], | ||
start_time: start_time, | ||
} | ||
end | ||
|
||
next unless line =~ /\[(\h{8}-\h{4}-\h{4}-\h{4}-\h{12})\] \[ReactOnRails\] PID:\d+ (\w+): (.*), (\d+\.\d+)ms/ | ||
|
||
# binding.pry | ||
request_id = ::Regexp.last_match(1) | ||
operation_name = ::Regexp.last_match(2) | ||
message = ::Regexp.last_match(3) | ||
duration = ::Regexp.last_match(4).to_f | ||
current_operation_in_stack = map_request_id_to_operation_stack[request_id].last | ||
|
||
if current_operation_in_stack[:operation_name] != operation_name || current_operation_in_stack[:message] != message | ||
raise "Unmatched operation name" | ||
end | ||
|
||
current_operation_in_stack[:duration] = duration | ||
if map_request_id_to_operation_stack[request_id].size > 1 | ||
map_request_id_to_operation_stack[request_id].pop | ||
map_request_id_to_operation_stack[request_id].last[:suboperations] << current_operation_in_stack | ||
end | ||
end | ||
|
||
# render map_request_id_to_operation_stack to json | ||
# replace request ids with paths | ||
@json_data = map_request_id_to_operation_stack.map do |request_id, operation_stack| | ||
path = map_request_id_to_path[request_id] | ||
{ path: path, operation_stack: operation_stack } | ||
end | ||
@json_data = @json_data.to_json | ||
|
||
# render the view in app/views/trace_visualizer/trace_visualizer/index.html.erb | ||
# with the json data | ||
render "index" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>React on Rails Performance Report</title> | ||
<style> | ||
body { | ||
font-family: Arial, sans-serif; | ||
padding: 20px; | ||
} | ||
.gantt-chart { | ||
width: 100%; | ||
position: relative; | ||
height: auto; | ||
border-left: 2px solid #000; | ||
border-top: 1px solid #000; | ||
} | ||
.task { | ||
position: absolute; | ||
height: 30px; | ||
background-color: #76A5AF; | ||
color: white; | ||
line-height: 30px; | ||
padding-left: 5px; | ||
border-radius: 5px; | ||
border: 1px solid #639; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="chartContainer" class="gantt-chart"></div> | ||
|
||
<script> | ||
window.OPERATIONS_TIMELINE = <%= raw @json_data %> | ||
</script> | ||
|
||
<script> | ||
// Sample JSON data | ||
const operations = window.OPERATIONS_TIMELINE; | ||
|
||
function findMinStartTime(requestData) { | ||
let minStartTime = Infinity; | ||
requestData.operation_stack.forEach(op => { | ||
minStartTime = Math.min(minStartTime, op.start_time); | ||
op.suboperations.forEach(subOp => { | ||
minStartTime = Math.min(minStartTime, subOp.start_time); | ||
}); | ||
}); | ||
return minStartTime; | ||
} | ||
|
||
function renderGanttChart(requestData, requestLevel) { | ||
const minStartTime = findMinStartTime(requestData); | ||
console.log(`MIN Start Time: ${minStartTime}`); | ||
let maxEndTime = 0; | ||
requestData.operation_stack.forEach((op, stackIndex) => { | ||
const startTime = (op.start_time - minStartTime); | ||
const duration = op.duration; | ||
maxEndTime = Math.max(maxEndTime, startTime + duration); | ||
|
||
op.suboperations.forEach(subOp => { | ||
const subStartTime = (subOp.start_time - minStartTime); | ||
const subDuration = subOp.duration; | ||
maxEndTime = Math.max(maxEndTime, subStartTime + subDuration); | ||
}); | ||
}); | ||
|
||
const scale = 1000 / maxEndTime; // Scaling to fit in 1000px width | ||
function renderOperation(op, startTime, top) { | ||
const duration = op.duration * scale; | ||
const task = document.createElement('div'); | ||
task.className = 'task'; | ||
task.style.top = `${top}px`; | ||
task.style.left = `${startTime*1000}px`; | ||
task.style.width = `${duration}px`; | ||
task.textContent = `${op.operation_name} (${op.duration.toFixed(2)} ms)`; | ||
// add tooltip | ||
task.title = `${op.operation_name}-${op.message} (${op.duration.toFixed(2)} ms)`; | ||
chartContainer.appendChild(task); | ||
|
||
let upperLevels = 0; | ||
op.suboperations.forEach((subOp, subIndex) => { | ||
const subStartTime = (subOp.start_time - minStartTime) * scale; | ||
const upperBranchLevel = renderOperation(subOp, subStartTime, top + 40); | ||
upperLevels = upperBranchLevel > upperLevels ? upperBranchLevel : upperLevels; | ||
}); | ||
return upperLevels + 1; | ||
} | ||
|
||
let levels = requestLevel; | ||
requestData.operation_stack.forEach((op) => { | ||
const startTime = (op.start_time - minStartTime) * scale; | ||
levels += renderOperation(op, startTime, levels * 40); // Increment top value for each operation, not suboperation | ||
}); | ||
levels++; | ||
return levels; | ||
} | ||
|
||
let requestLevels = 0; | ||
operations.forEach(requestData => { | ||
const currentRequestLevels = renderGanttChart(requestData, requestLevels) | ||
requestLevels += currentRequestLevels; | ||
}); | ||
chartContainer.height = `${requestLevels * 40 + 80}px` | ||
</script> | ||
</body> | ||
</html> | ||
Comment on lines
+1
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comprehensive Review of the Trace Visualizer HTML and JavaScript The overall structure and implementation of the HTML and JavaScript for the Trace Visualizer is robust and well-organized. The use of inline JavaScript to manipulate DOM elements for dynamic Gantt chart rendering is appropriate given the context of a performance visualization tool. The CSS styling is minimalistic yet effective, ensuring that the visual elements are clear and concise. However, there are a few areas that could be improved:
Overall, the implementation meets the basic requirements but could be enhanced by addressing the above points. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# frozen_string_literal: true | ||
|
||
TraceVisualizer::Engine.routes.draw do | ||
get "/", to: "trace_visualizer#index" | ||
end |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -18,20 +18,21 @@ def server_rendering_component_js_code( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
react_component_name: nil, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
render_options: nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ReactOnRails::Utils.with_trace(react_component_name) do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
config_server_bundle_js = ReactOnRails.configuration.server_bundle_js_file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
config_server_bundle_js = ReactOnRails.configuration.server_bundle_js_file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if render_options.prerender == true && config_server_bundle_js.blank? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
msg = <<~MSG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The `prerender` option to allow Server Side Rendering is marked as true but the ReactOnRails configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for `server_bundle_js_file` is nil or not present in `config/initializers/react_on_rails.rb`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Set `config.server_bundle_js_file` to your javascript bundle to allow server side rendering. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Read more at https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
MSG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise ReactOnRails::Error, msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if render_options.prerender == true && config_server_bundle_js.blank? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
msg = <<~MSG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The `prerender` option to allow Server Side Rendering is marked as true but the ReactOnRails configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for `server_bundle_js_file` is nil or not present in `config/initializers/react_on_rails.rb`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Set `config.server_bundle_js_file` to your javascript bundle to allow server side rendering. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Read more at https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
MSG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise ReactOnRails::Error, msg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
js_code_renderer.render(props_string, rails_context, redux_stores, react_component_name, render_options) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+21
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactor tracing and error handling in server-side rendering. The addition of tracing within the - Read more at https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/
+ Please check the configuration or visit https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/ for more information. Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
js_code_renderer.render(props_string, rails_context, redux_stores, react_component_name, render_options) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def render(props_string, rails_context, redux_stores, react_component_name, render_options) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimize log file processing and error handling.
The
index
method effectively parses log files and maps operations. Consider handling potential errors such as file not found or unreadable logs to enhance robustness.Committable suggestion