Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Perfm

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Requirements

  • Ruby: MRI 3.2+

This is because the GVL instrumentation API was added in 3.2.0. Perfm makes use of the gvl_timing gem to capture per-thread timings for each GVL state.

Installation

Add perfm to the Gemfile.

gem'perfm'

To set up GVL instrumentation run the following command:

bin/rails generate perfm:install

This will create a migration file with a table to store the GVL metrics. Run the migration and configure the gem as described below.

Configuration

Configure Perfm in an initializer:

Perfm.configuredo |config|
config.enabled=trueconfig.monitor_gvl=trueconfig.storage=:localendPerfm.setup!

When monitor_gvl is enabled, perfm adds a Rack middleware to log GVL metrics for each request. The metrics are stored in the database.

We just need around 20000 datapoints(i.e requests) to get an idea of the app's workload. So the monitor_gvl config can be disabled after that. We can control the value via an ENV variable if we prefer.

Analysis

Run the following in the Rails console to analyze the GVL metrics.

gvl_metrics_analyzer=Perfm::GvlMetricsAnalyzer.new(start_time: 5.days.ago,end_time: Time.current)gvl_metrics_analyzer.analyze# Write to fileFile.write("tmp/perfm/gvl_analysis_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json",JSON.pretty_generate(gvl_metrics_analyzer.analyze))
Example output
{
"summary": {
"total_io_percentage": 56.34,
"average_response_time_ms": 128.17,
"average_stall_ms": 17.77,
"average_gc_ms": 10.09,
"request_count": 84,
"time_range": {
"start_time": "2025-10-22 12:33:36 UTC",
"end_time": "2025-10-27 12:33:36 UTC",
"duration_seconds": 432000
}
},
"percentiles": {
"overall": "84 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 8
},
"p50-60": {
"cpu": 60.1,
"io": 31.7,
"stall": 2.8,
"gc": 3.0,
"total": 94.6,
"io%": "34.5%",
"count": 8
},
"p90-99": {
"cpu": 128.3,
"io": 279.7,
"stall": 61.2,
"gc": 22.3,
"total": 469.2,
"io%": "68.6%",
"count": 8
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"action_breakdowns": {
"#": {
"overall": "43 requests",
"p0-10": {
"cpu": 0.5,
"io": 0.1,
"stall": 0.0,
"gc": 0.0,
"total": 0.6,
"io%": "16.7%",
"count": 4
},
"p50-60": {
"cpu": 0.8,
"io": 0.3,
"stall": 0.1,
"gc": 0.0,
"total": 1.2,
"io%": "27.3%",
"count": 4
},
"p90-99": {
"cpu": 2.0,
"io": 14.4,
"stall": 1.3,
"gc": 1.1,
"total": 17.7,
"io%": "87.8%",
"count": 4
},
"p99-99.9": {
"cpu": 0.0,
"io": 0.0,
"stall": 0.0,
"gc": 0.0,
"total": 0.0,
"io%": "0.0%",
"count": 0
},
"p99.9-100": {
"cpu": 56.6,
"io": 21.8,
"stall": 91.0,
"gc": 68.4,
"total": 169.4,
"io%": "27.8%",
"count": 1
}
},
"api/v1/projects#show": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 229.1,
"io": 101.5,
"stall": 0.4,
"gc": 26.2,
"total": 331.0,
"io%": "30.7%",
"count": 1
}
},
"api/v1/projects/runs#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 166.5,
"io": 747.4,
"stall": 0.4,
"gc": 7.9,
"total": 914.3,
"io%": "81.8%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#index": {
"overall": "4 requests",
"p99.9-100": {
"cpu": 85.0,
"io": 192.1,
"stall": 0.4,
"gc": 15.4,
"total": 277.5,
"io%": "69.3%",
"count": 1
}
},
"api/v1/projects/runs#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 104.6,
"io": 158.8,
"stall": 0.6,
"gc": 2.1,
"total": 264.0,
"io%": "60.3%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/result_histories#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 88.1,
"io": 312.3,
"stall": 79.2,
"gc": 14.4,
"total": 479.6,
"io%": "78.0%",
"count": 1
}
},
"api/v1/projects/runs/test_entities/outcomes#index": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 119.7,
"io": 234.8,
"stall": 150.5,
"gc": 16.1,
"total": 505.0,
"io%": "66.2%",
"count": 1
}
},
"api/v1/projects/runs/test_entities#show": {
"overall": "3 requests",
"p99.9-100": {
"cpu": 123.4,
"io": 257.9,
"stall": 155.2,
"gc": 15.9,
"total": 536.5,
"io%": "67.6%",
"count": 1
}
}
}
}

This will print the following metrics:

  • total_io_percentage: Percentage of time spent doing I/O operations
  • total_io_and_stall_percentage: Percentage of time spent in I/O operations (idle time) and GVL stalls combined. See this blog for more details.
  • average_response_time_ms: Average response time in milliseconds per request
  • average_stall_ms: Average GVL stall time in milliseconds per request
  • average_gc_ms: Average garbage collection time in milliseconds per request
  • request_count: Total number of requests analyzed
  • time_range: Details about the analysis period including:
    • start_time
    • end_time
    • duration_seconds

After analysis, we can drop the table to save space. The following command generates a migration to drop the table.

bin/rails generate perfm:uninstall

Beta Features

The following features are currently in beta and may have limited functionality or be subject to change.

Sidekiq queue latency monitor

The queue latency monitor tracks Sidekiq queue times and raises alerts when the queue latency exceed their thresholds. To enable this feature, set config.monitor_sidekiq_queues = true in the Perfm configuration.

Perfm.configuredo |config|
# Other configurations...config.monitor_sidekiq_queues=trueend

When enabled, Perfm will monitor the Sidekiq queues and raise a Perfm::Errors::LatencyExceededError when the queue latency exceeds the threshold.

Queue Naming Convention

Perfm expects queues that need latency monitoring to be named in the following format. If the queue is not named in this format, it will not be considered.

  • within_X_seconds (e.g., within_5_seconds)
  • within_X_minutes (e.g., within_2_minutes)
  • within_X_hours (e.g., within_1_hours)

Sidekiq GVL Instrumentation

To enable GVL instrumentation for Sidekiq, first run the generator to add migrations for the required table to store the metrics.

bin/rails generate perfm:sidekiq_gvl_metrics

Then enable the monitor_sidekiq_gvl configuration.

Perfm.configuredo |config|
config.monitor_sidekiq_gvl=trueend

When enabled, Perfm will collect GVL metrics at a job level, similar to how it collects metrics for HTTP requests. This can be used to analyze GVL metrics specifically for Sidekiq queues to understand their I/O characteristics.

Perfm::SidekiqGvlMetric.calculate_queue_io_percentage("within_5_seconds")

About

Perfm aims to be a performance monitoring tool for Ruby on Rails applications. Currently, it has support for GVL instrumentation and provides analytics to help optimize Puma thread concurrency settings based on the collected GVL data.

Resources

Stars

50 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages