Skip to content
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

Conversation

AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Jun 24, 2024

Summary

image

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file
    Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Introduced Trace Visualizer for visualizing operation timelines as a Gantt chart.
    • Added a Rake task to serve the Trace Visualizer on http://localhost:5200.
  • Enhancements

    • Improved tracing and logging capabilities in server rendering components.
    • Refined error-checking and tracing logic within JavaScript rendering methods.
  • Refactor

    • Updated methods to include tracing blocks and enhanced logging for server-side rendering.
    • Adjusted internal methods to handle new parameters for tracing and logging.

Copy link
Contributor

coderabbitai bot commented Jun 24, 2024

Walkthrough

The recent update introduces a Trace Visualizer into the application, enabling visualization of operations through a Gantt chart. Key changes include implementation of a new controller, view, and routes for the visualizer, along with tracing enhancements within the react_on_rails framework. A new Rake task is also added to easily start a server for the visualizer. These changes collectively aim to enhance debugging and performance analysis.

Changes

File(s) Change Summary
app/controllers/trace_visualizer/... Added TraceVisualizerController to handle log reading, data parsing, and JSON response for visualization.
app/views/trace_visualizer/... Introduced index.html.erb to render a web page displaying a Gantt chart for operations timeline data.
config/routes.rb Added route for TraceVisualizer engine to direct requests to trace_visualizer#index action.
lib/react_on_rails.rb Added require 'trace_visualizer/engine' statement.
lib/react_on_rails/helper.rb Refactored methods to include tracing via ReactOnRails::Utils.with_trace; modified server rendering reset logic.
lib/react_on_rails/server_rendering_js_code.rb Added tracing block to server_rendering_component_js_code method, including error-checking logic.
lib/react_on_rails/server_rendering_pool/... Modified reset_pool_if_server_bundle_was_modified signature and refactored logic; added tracing.
lib/react_on_rails/utils.rb Introduced with_trace method for conditional logging and time tracking.
lib/tasks/trace_visualizer.rake Added a Rake task to start a server for Trace Visualizer engine using Puma.
lib/trace_visualizer/engine.rb Introduced TraceVisualizer module with Engine class for Rails engine namespace isolation.

Sequence Diagrams

Trace Visualization Flow

sequenceDiagram
    participant User
    participant Browser
    participant TraceVisualizerController
    participant TraceVisualizer::Engine

    User->>Browser: Request data visualization
    Browser->>TraceVisualizerController: GET /trace_visualizer
    TraceVisualizerController->>TraceVisualizer::Engine: Process log data
    TraceVisualizer::Engine->>TraceVisualizerController: Return processed data
    TraceVisualizerController->>Browser: Render JSON response
    Browser->>User: Display Gantt chart
Loading

Component Rendering with Trace

sequenceDiagram
    participant Client
    participant ReactOnRails
    participant RenderHelper

    Client->>ReactOnRails: Request render React component
    ReactOnRails->>RenderHelper: Call internal_react_component with trace
    RenderHelper->>ReactOnRails: Render component with trace
    ReactOnRails->>Client: Return rendered component
Loading

Poem

In the land where codes and logs unite,
A visualizer springs to life in the night.
Gantt charts bloom, tracing every move,
With enhanced insights, they beautifully groove.
Debugging's now a clearer stage,
Thanks to the updates on this new page.
🌟 Happy coding nights from CodeRabbit's sage! 🐇📜


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@AbanoubGhadban AbanoubGhadban marked this pull request as draft June 24, 2024 15:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 2f023ec and dc52320.

Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
Files selected for processing (10)
  • app/controllers/trace_visualizer/trace_visualizer_controller.rb (1 hunks)
  • app/views/trace_visualizer/trace_visualizer/index.html.erb (1 hunks)
  • config/routes.rb (1 hunks)
  • lib/react_on_rails.rb (1 hunks)
  • lib/react_on_rails/helper.rb (2 hunks)
  • lib/react_on_rails/server_rendering_js_code.rb (1 hunks)
  • lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (3 hunks)
  • lib/react_on_rails/utils.rb (1 hunks)
  • lib/tasks/trace_visualizer.rake (1 hunks)
  • lib/trace_visualizer/engine.rb (1 hunks)
Files not reviewed due to errors (2)
  • lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (no review received)
  • lib/react_on_rails/helper.rb (no review received)
Files skipped from review due to trivial changes (2)
  • config/routes.rb
  • lib/trace_visualizer/engine.rb
Additional comments not posted (1)
lib/react_on_rails.rb (1)

29-29: Integration of Trace Visualizer engine.

The addition of require 'trace_visualizer/engine' is correctly placed among other require statements, ensuring that the Trace Visualizer is loaded with the React on Rails framework. This is crucial for the functionality of the new dashboard.

Comment on lines +1 to +108
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>
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

  1. JavaScript Modularity: The JavaScript code is embedded directly within the HTML document. For better maintainability and scalability, consider extracting this into separate JavaScript files. This not only cleans up the HTML file but also allows for easier testing and reusability of the JavaScript code.
  2. Error Handling in JavaScript: While the script handles the basic flow well, adding error handling around JSON parsing and DOM manipulations could prevent runtime errors and improve the robustness of the page.
  3. Accessibility: Ensure that the dynamically generated DOM elements (tasks in the Gantt chart) are accessible. This includes proper roles, labels, and keyboard navigability.
  4. Performance Considerations: For large datasets, the current implementation might become slow as it involves multiple nested loops and DOM manipulations. Consider optimizing the rendering logic or using a virtual DOM approach to handle larger datasets efficiently.

Overall, the implementation meets the basic requirements but could be enhanced by addressing the above points.

Comment on lines +3 to +13
namespace :react_on_rails do
task trace_visualizer: :environment do
require 'rack/handler/puma' # Using Puma as the server
Rack::Handler::Puma.run TraceVisualizer::Engine, Port: 5200 do |server|
puts 'Serving Trace Visualizer on http://localhost:5200'
trap(:INT) do
server.stop
puts 'Shutting down server'
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper server shutdown handling.

While the task correctly sets up the server and handles SIGINT for a graceful shutdown, consider also handling SIGTERM for unexpected terminations. This ensures that the server shuts down gracefully in more scenarios.

+      trap(:TERM) do
+        server.stop
+        puts 'Shutting down server due to SIGTERM'
+      end
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
namespace :react_on_rails do
task trace_visualizer: :environment do
require 'rack/handler/puma' # Using Puma as the server
Rack::Handler::Puma.run TraceVisualizer::Engine, Port: 5200 do |server|
puts 'Serving Trace Visualizer on http://localhost:5200'
trap(:INT) do
server.stop
puts 'Shutting down server'
end
end
end
namespace :react_on_rails do
task trace_visualizer: :environment do
require 'rack/handler/puma' # Using Puma as the server
Rack::Handler::Puma.run TraceVisualizer::Engine, Port: 5200 do |server|
puts 'Serving Trace Visualizer on http://localhost:5200'
trap(:INT) do
server.stop
puts 'Shutting down server'
end
trap(:TERM) do
server.stop
puts 'Shutting down server due to SIGTERM'
end
end
end

Comment on lines +21 to +34
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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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 server_rendering_component_js_code method enhances debugging capabilities. However, the error message could be made more user-friendly and actionable.

-              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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)
ReactOnRails::Utils.with_trace(react_component_name) do
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.
Please check the configuration or visit https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/ for more information.
MSG
raise ReactOnRails::Error, msg
end
js_code_renderer.render(props_string, rails_context, redux_stores, react_component_name, render_options)

Comment on lines +5 to +75
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
Copy link
Contributor

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.

+      if !File.exist?(log_file_path)
+        raise "Log file not found at #{log_file_path}"
+      end
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
def index
log_file_path = "./log1.log"
if !File.exist?(log_file_path)
raise "Log file not found at #{log_file_path}"
end
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant