Skip to content

Conversation

@rileyseaburg
Copy link
Contributor

@rileyseaburg rileyseaburg commented Oct 29, 2025

Overview

This PR implements a Rust-based widget renderer extension that delivers 12.9x faster rendering performance and 12.1x faster full HTTP response times over the existing ERB template system for JavaScript widget generation in the Touchpoints application.

📊 Benchmark Results

Direct Rendering Performance (Controller Context)

Metric Rust ERB Improvement
Average Render Time 2.658ms 34.387ms 12.9x faster
Throughput 376.19 renders/s 29.08 renders/s 12.9x higher
Total Time (100 renders) 265.82ms 3.44s 12.9x faster

Production Performance (Full HTTP Stack)

Metric Rust ERB Improvement
Average Response Time 58.45ms 707.9ms 12.1x faster
Throughput 17.11 req/s 1.41 req/s 12.1x higher
Time Saved per Request - 649.45ms 91.7% reduction
Total Time (50 requests) 2.92s 35.40s 12.1x faster

Test Environment

  • Rails Version: 8.0.2.1
  • Ruby Version: 3.4.7 (with YJIT enabled)
  • Rust Version: cargo 1.91.0
  • Platform: Docker (x86-64 Linux for Cloud Foundry)
  • Test Form: Form ID 8 (UUID: fb770934)
  • Output Size: 4,189 lines (~133KB JavaScript)

Benchmark Endpoints

# Direct render - Rust
curl -s http://localhost:3000/benchmark/widget | jq .

# Direct render - ERB  
curl -s http://localhost:3000/benchmark/widget/erb | jq .

# HTTP full stack
curl -s http://localhost:3000/benchmark/widget/http | jq .

See BENCHMARK_RESULTS.md for detailed analysis and methodology.

Key Features

🚀 Performance Optimization

  • 12.9x faster direct rendering (2.658ms vs 34.387ms)
  • 12.1x faster full HTTP request handling (58.45ms vs 707.9ms)
  • 376.19 renders/second in isolated benchmarks
  • 91.7% reduction in HTTP response time
  • Context-independent rendering (no Rails request/response required)

🔧 Technical Implementation

  • Compile-Time Asset Embedding: 4,020-line USWDS bundle embedded via include_str!()
  • Zero File I/O: All templates compiled into 558KB binary
  • Type-Safe: Rust structs ensure data integrity
  • Backward Compatible: 100% identical output to ERB (4,189 lines)
  • Automatic Fallback: Graceful ERB fallback if Rust unavailable

🏗️ Architecture

  • Modular Design: Clean separation of concerns
    • lib.rs: Main entry point and Ruby class binding
    • form_data.rs: Data structure parsing from Ruby hashes (computes prefix from load_css)
    • template_renderer.rs: JavaScript template generation with embedded USWDS bundle
  • Rutie FFI: Ruby-Rust integration via Rutie 0.9.0
  • Error Handling: Proper error handling with fallback mechanisms

📦 Deployment Ready

  • Cloud Foundry Compatible: Compiled for x86-64 architecture
  • Binary Included: 558KB .so file committed to git
  • No Build Required: Ready to deploy on Cloud Foundry

Files Changed

Core Implementation

  • ext/widget_renderer/: Complete Rust extension directory
    • src/lib.rs: FFI bridge and Ruby integration
    • src/form_data.rs: Data parsing and prefix computation
    • src/template_renderer.rs: JavaScript generation with USWDS bundle
    • widget-uswds-bundle.js: 4,020-line USWDS JavaScript bundle
    • widget_renderer.so: Compiled binary (x86-64, 558KB)
  • app/models/form.rb: Updated with touchpoints_js_string method (Rust/ERB fallback)
  • app/controllers/touchpoints_controller.rb: Use form.touchpoints_js_string
  • config/routes.rb: Added benchmark endpoints

Documentation & Testing

  • BENCHMARK_RESULTS.md: Comprehensive performance analysis and methodology
  • RUST_WIDGET_IMPLEMENTATION.md: Technical documentation and deployment guide
  • app/controllers/benchmark_controller.rb: Direct render, ERB, and HTTP benchmarks

Build Configuration

  • Gemfile: Added Rutie 0.9.0 for Ruby-Rust FFI
  • ext/widget_renderer/Cargo.toml: Rust build configuration
  • ext/widget_renderer/Makefile: Build automation

Benefits

1. Performance

  • 12.9x faster widget generation reduces server load and improves user experience
  • Sub-60ms response times enable real-time widget embedding
  • Lower infrastructure costs: ~12x reduction in server requirements

2. Scalability

  • Direct render capacity: 376.19 renders/s vs 29.08 renders/s (12.9x improvement)
  • HTTP request capacity: 17.11 req/s vs 1.41 req/s (12.1x improvement)
  • 1M requests/day: Processes in 16.2 hours vs 196.6 hours (saves 180.4 CPU hours/day)
  • Reduced server count: 1 server vs 12 servers for same load

3. Reliability

  • Context-independent: Works outside HTTP request cycle (background jobs, APIs)
  • No template dependencies: All assets compiled into binary
  • Automatic fallback: Zero downtime if Rust extension unavailable

4. Developer Experience

  • Faster tests: Multiple benchmark endpoints for performance testing
  • Clear profiling: Isolated render vs full HTTP metrics
  • Production-ready: Compiled binary included, no build step needed

Testing

Benchmark Endpoints (Development Only)

# Direct render - Rust (fastest)
curl -s http://localhost:3000/benchmark/widget | jq .

# Direct render - ERB (for comparison)
curl -s http://localhost:3000/benchmark/widget/erb | jq .

# HTTP benchmark (full Rails stack)
curl -s http://localhost:3000/benchmark/widget/http | jq .

Fallback Testing

The implementation includes comprehensive fallback mechanisms. If the Rust extension fails to load, the system automatically uses the existing ERB-based rendering, ensuring zero downtime.

Functional Testing

# Test actual widget output
curl http://localhost:3000/touchpoints/fb770934.js

# Verify output size
curl -s http://localhost:3000/touchpoints/fb770934.js | wc -l
# Should output: 4189 lines

Deployment Notes

Cloud Foundry

  • Binary is compiled for x86-64 architecture
  • No build step required during deployment
  • Binary located at: ext/widget_renderer/widget_renderer.so
  • Size: 558KB

Verification Steps

  1. Deploy to Cloud Foundry
  2. Check Rails logs for: WidgetRenderer extension loaded successfully
  3. Test widget endpoint: curl https://[app-url]/touchpoints/[form-id].js
  4. Verify performance in production metrics

Security

CSRF Protection

  • Benchmark endpoints properly protected with CSRF tokens
  • Development-only endpoints (enforced at route and controller level)
  • No security vulnerabilities introduced

Future Enhancements

Performance Optimization Opportunities

  1. Caching: Cache rendered JavaScript for unchanged forms
  2. Parallel Rendering: Generate widgets for multiple forms concurrently
  3. Custom Bundles: Support different USWDS configurations per form
  4. Minification: Optional JavaScript minification during rendering

Feature Extensions

  1. Source Maps: Generate source maps for easier JavaScript debugging
  2. Metrics Collection: Track rendering performance in production
  3. A/B Testing: Compare Rust vs ERB performance in production
  4. Progressive Rollout: Feature flag for gradual rollout

*Ready for review and testing 🎉

See BENCHMARK_RESULTS.md for comprehensive performance analysis and RUST_WIDGET_IMPLEMENTATION.md for technical implementation details.

- Implement Rust extension using Rutie for Ruby-Rust FFI
- Add widget_renderer extension with modular architecture:
  - lib.rs: Main entry point and Ruby class binding
  - form_data.rs: Data structure parsing from Ruby hashes
  - template_renderer.rs: JavaScript template generation
- Update Form model with fallback mechanism for backward compatibility
- Add initializer for graceful extension loading
- Include comprehensive documentation and test files
- Configure build system with Cargo.toml and extconf.rb

This implementation provides faster JavaScript widget generation while
maintaining full compatibility with existing ERB-based rendering.
Copilot AI review requested due to automatic review settings October 29, 2025 19:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR integrates Rust-based widget rendering to improve performance by replacing CPU-intensive ERB template rendering. The implementation uses the rutie gem to bridge Ruby and Rust, allowing JavaScript widget generation to be 10-100x faster.

Key changes:

  • Adds Rust extension (widget_renderer) using rutie for high-performance JavaScript widget generation
  • Implements fallback to ERB rendering when Rust extension is unavailable
  • Upgrades Ruby version from 3.2.8 to 3.4.7 across Gemfile and CI configuration

Reviewed Changes

Copilot reviewed 15 out of 19 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Gemfile Adds rutie gem dependency and upgrades Ruby version to 3.4.7
Gemfile.lock Lock file updates for rutie, ruby-lsp dependencies and Ruby version
.circleci/config.yml Updates CI to use Ruby 3.2.8 Docker image and removes browser-tools orb
process.yml New CI process configuration file with cron tasks and build jobs
ext/widget_renderer/Cargo.toml Cargo configuration for widget_renderer Rust crate
ext/widget_renderer/Cargo.lock Rust dependencies lock file
ext/widget_renderer/src/lib.rs Main Rust entry point defining Ruby class interface
ext/widget_renderer/src/form_data.rs Ruby-to-Rust data conversion layer
ext/widget_renderer/src/template_renderer.rs JavaScript template rendering logic in Rust
ext/widget_renderer/extconf.rb Ruby extension configuration for building Rust library
ext/widget_renderer/Makefile Generated makefile for extension compilation
config/initializers/widget_renderer.rb Rails initializer to load Rust extension with fallback handling
app/models/form.rb Updates touchpoints_js_string method to use Rust renderer when available
test_rust_extension.rb Test script for validating Rust extension functionality
rutie-implementation.md Documentation for rutie-based implementation approach
rust-widget-service.md Documentation for alternative Rust microservice approach
Cargo.toml Workspace configuration for Rust crates
Cargo.lock Workspace-level Rust dependencies
.gitignore Adds Rust target directories to gitignore

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

#### Start of system configuration section. ####

srcdir = .
topdir = /opt/homebrew/Cellar/ruby/3.4.7/include/ruby-3.4.0
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Machine-specific absolute paths in Makefile: This file contains hardcoded paths specific to a developer's local Homebrew installation (/opt/homebrew/Cellar/ruby/3.4.7). Generated Makefiles should not be committed to version control as they are environment-specific. Add ext/widget_renderer/Makefile to .gitignore and let extconf.rb generate it during installation.

Copilot uses AI. Check for mistakes.
rileyseaburg and others added 6 commits October 29, 2025 15:09
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Riley Seaburg <[email protected]>
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Riley Seaburg <[email protected]>
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Riley Seaburg <[email protected]>
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Riley Seaburg <[email protected]>
- Implemented Rust-based widget renderer as Ruby extension using Rutie FFI
- Embedded 4,020-line USWDS JavaScript bundle at compile time via include_str!()
- Created comprehensive benchmarks comparing Rust vs ERB performance
- Added HTTP and direct render benchmark endpoints
- Compiled binary for x86-64 (Cloud Foundry deployment)

Performance Results:
- HTTP requests: 58.45ms (Rust) vs 707.9ms (ERB) = 12.1x faster
- Direct render: 2.235ms (Rust) vs unable to benchmark ERB (context-dependent)
- Throughput: 17.11 req/s (Rust) vs 1.41 req/s (ERB)
- 91.7% reduction in response time

Technical Implementation:
- Rust extension compiled to 558KB .so file (x86-64)
- Full backward compatibility with ERB (identical 4,189-line output)
- Context-independent rendering (no Rails request/response required)
- Automatic ERB fallback if Rust extension unavailable
- Prefix computed from load_css in Rust (no Ruby model changes)

Files Added:
- BENCHMARK_RESULTS.md: Comprehensive performance analysis
- RUST_WIDGET_IMPLEMENTATION.md: Technical documentation
- app/controllers/benchmark_controller.rb: Performance testing endpoints
- ext/widget_renderer/: Complete Rust extension source code
- ext/widget_renderer/widget_renderer.so: Compiled binary (x86-64)
- ext/widget_renderer/widget-uswds-bundle.js: Embedded USWDS bundle

Files Modified:
- app/controllers/touchpoints_controller.rb: Use form.touchpoints_js_string
- app/models/form.rb: Add Rust/ERB fallback logic (no Ruby method changes)
- config/routes.rb: Add benchmark routes
- ext/widget_renderer/src/form_data.rs: Compute prefix from load_css
- ext/widget_renderer/src/template_renderer.rs: USWDS bundle embedding
rileyseaburg and others added 2 commits November 4, 2025 10:51
- Remove skip_before_action :verify_authenticity_token
- Add before_action to ensure development environment only
- Benchmark endpoints now properly protected and restricted to development mode
- Addresses CodeQL security alert #41
Co-authored-by: Copilot <[email protected]>
Signed-off-by: Riley Seaburg <[email protected]>
@rileyseaburg rileyseaburg requested a review from Copilot November 4, 2025 17:09
@rileyseaburg rileyseaburg self-assigned this Nov 4, 2025
@rileyseaburg rileyseaburg added the enhancement New feature or request label Nov 4, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 21 out of 27 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Sets the clicked element to the close button
// so esc key always closes modal
if (event.type === "keydown" && targetModal !== null) {
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

Variable 'targetModal' cannot be of type null, but it is compared to an expression of type null.

Suggested change
if (event.type === "keydown" && targetModal !== null) {
if (event.type === "keydown") {

Copilot uses AI. Check for mistakes.
toString: function () {
return "[object WrappedHTMLObject]";
},
info: "This is a wrapped HTML object. See https://developer.mozilla.or" + "g/en-US/Firefox_OS/Security/Security_Automation for more."
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

This string appears to be missing a space after 'https://developer.mozilla.or'.

Copilot uses AI. Check for mistakes.
- Added widget_erb_benchmark endpoint to benchmark ERB in controller context
- ERB can be tested with Rails helpers available (not in complete isolation)
- Updated benchmark results with direct render comparison

Direct Render Results:
- Rust: 2.658ms avg (376.19 renders/s)
- ERB: 34.387ms avg (29.08 renders/s)
- Performance: 12.9x faster with Rust

HTTP Request Results (unchanged):
- Rust: 58.45ms avg (17.11 req/s)
- ERB: 707.9ms avg (1.41 req/s)
- Performance: 12.1x faster with Rust
Copy link

@pburkholder pburkholder Nov 4, 2025

Choose a reason for hiding this comment

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

I'm generally reluctant to bundle binaries in with the source code. Since there's no supported Rust buildpack, I don't think there's a good way to avoid that. This file will also need to be added to the list of binary files, a la, https://github.com/GSA/made-in-america-django/blob/develop/.allstar/binary_artifacts.yaml

Changed raw string literal delimiters from r#"..."# to r###"..."###
to avoid conflicts with JavaScript code containing "# sequences.
This fixes all compilation errors related to unterminated strings
and unknown character escapes in regex patterns.
- Add mock request setup in Form#touchpoints_js_string to avoid 'undefined method host for nil' errors
- Create ActionDispatch::Request with proper host, port, and protocol from Rails config
- Fall back to action_mailer.default_url_options if action_controller options not set
- Fixes 8 failing specs in forms_spec.rb related to widget JavaScript generation
Enable public file server for test environment to ensure proper serving of static assets during testing, complementing the existing cache-control headers configuration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants