Created
March 8, 2026 14:30
-
-
Save ksuderman/b51da6b37faae0079afedb9bc97fbf61 to your computer and use it in GitHub Desktop.
Compare Pulsar and Direct GCP Batch job runners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Pulsar vs GCP Batch: Runtime Comparison</title> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: #f5f7fa; | |
| color: #1a1a2e; | |
| padding: 2rem; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| font-size: 1.8rem; | |
| margin-bottom: 0.3rem; | |
| } | |
| .subtitle { | |
| color: #666; | |
| font-size: 0.95rem; | |
| margin-bottom: 2rem; | |
| } | |
| .summary-cards { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .card { | |
| background: #fff; | |
| border-radius: 10px; | |
| padding: 1.2rem 1.5rem; | |
| box-shadow: 0 1px 4px rgba(0,0,0,0.08); | |
| } | |
| .card .label { font-size: 0.8rem; color: #888; text-transform: uppercase; letter-spacing: 0.05em; } | |
| .card .value { font-size: 1.8rem; font-weight: 700; margin-top: 0.2rem; } | |
| .card .detail { font-size: 0.85rem; color: #666; margin-top: 0.2rem; } | |
| .card.batch .value { color: #3b82f6; } | |
| .card.pulsar .value { color: #f59e0b; } | |
| .card.overhead .value { color: #ef4444; } | |
| .chart-container { | |
| background: #fff; | |
| border-radius: 10px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| box-shadow: 0 1px 4px rgba(0,0,0,0.08); | |
| } | |
| .chart-container h2 { | |
| font-size: 1.1rem; | |
| margin-bottom: 0.3rem; | |
| } | |
| .chart-container .chart-desc { | |
| font-size: 0.85rem; | |
| color: #888; | |
| margin-bottom: 1rem; | |
| } | |
| .chart-row { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1.5rem; | |
| } | |
| @media (max-width: 800px) { | |
| .chart-row { grid-template-columns: 1fr; } | |
| } | |
| .footer { | |
| text-align: center; | |
| color: #aaa; | |
| font-size: 0.8rem; | |
| margin-top: 2rem; | |
| padding-top: 1rem; | |
| border-top: 1px solid #e5e7eb; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Pulsar vs Direct GCP Batch</h1> | |
| <p class="subtitle">Variant analysis on WGS PE data — 12-step workflow — 2026-03-07</p> | |
| <div class="summary-cards"> | |
| <div class="card batch"> | |
| <div class="label">GCP Batch (direct)</div> | |
| <div class="value">19.2 min</div> | |
| <div class="detail">12 steps, all succeeded</div> | |
| </div> | |
| <div class="card pulsar"> | |
| <div class="label">Pulsar (AMQP sidecar)</div> | |
| <div class="value">30.5 min</div> | |
| <div class="detail">12 steps, all succeeded</div> | |
| </div> | |
| <div class="card overhead"> | |
| <div class="label">Pulsar Overhead</div> | |
| <div class="value">+11.3 min</div> | |
| <div class="detail">+59% — ~57s per step (median)</div> | |
| </div> | |
| </div> | |
| <div class="chart-container"> | |
| <h2>Per-Step Execution Time</h2> | |
| <p class="chart-desc">Time from previous step completion to this step's completion. Includes scheduling, staging, compute, and output collection.</p> | |
| <canvas id="barChart" height="100"></canvas> | |
| </div> | |
| <div class="chart-row"> | |
| <div class="chart-container"> | |
| <h2>Per-Step Overhead (seconds)</h2> | |
| <p class="chart-desc">Additional time Pulsar takes compared to direct Batch for each step. Negative = Pulsar was faster.</p> | |
| <canvas id="overheadChart" height="140"></canvas> | |
| </div> | |
| <div class="chart-container"> | |
| <h2>Cumulative Workflow Time</h2> | |
| <p class="chart-desc">Running total of elapsed time as each step completes.</p> | |
| <canvas id="cumulativeChart" height="140"></canvas> | |
| </div> | |
| </div> | |
| <div class="chart-row"> | |
| <div class="chart-container"> | |
| <h2>Time Breakdown</h2> | |
| <p class="chart-desc">Total workflow time split across tools.</p> | |
| <canvas id="pieChartBatch" height="160"></canvas> | |
| </div> | |
| <div class="chart-container"> | |
| <h2>Time Breakdown</h2> | |
| <p class="chart-desc">Total workflow time split across tools.</p> | |
| <canvas id="pieChartPulsar" height="160"></canvas> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| Galaxy 26.1 · GCP Batch us-east4 · Input: ERR3485802 + genome.genbank | |
| </div> | |
| <script> | |
| const tools = [ | |
| 'fastp', 'snpEff_build_gb', 'bwa_mem', 'samtools_view', 'samtools_stats', | |
| 'picard_MarkDup', 'lofreq_viterbi', 'multiqc', 'lofreq_indelqual', | |
| 'lofreq_call', 'lofreq_filter', 'snpEff' | |
| ]; | |
| const batchTimes = [137, 35, 109, 116, 114, 3, 114, 28, 94, 137, 116, 148]; | |
| const pulsarTimes = [205, 23, 185, 195, 205, 20, 187, 12, 187, 204, 194, 216]; | |
| const overhead = pulsarTimes.map((p, i) => p - batchTimes[i]); | |
| const blue = '#3b82f6'; | |
| const blueLight = '#93bbfd'; | |
| const amber = '#f59e0b'; | |
| const amberLight = '#fcd679'; | |
| const red = '#ef4444'; | |
| const green = '#22c55e'; | |
| const chartFont = { family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }; | |
| Chart.defaults.font.family = chartFont.family; | |
| Chart.defaults.font.size = 12; | |
| // --- Bar chart: side by side --- | |
| new Chart(document.getElementById('barChart'), { | |
| type: 'bar', | |
| data: { | |
| labels: tools, | |
| datasets: [ | |
| { | |
| label: 'GCP Batch (direct)', | |
| data: batchTimes, | |
| backgroundColor: blue, | |
| borderRadius: 4, | |
| }, | |
| { | |
| label: 'Pulsar (AMQP sidecar)', | |
| data: pulsarTimes, | |
| backgroundColor: amber, | |
| borderRadius: 4, | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { position: 'top' }, | |
| tooltip: { | |
| callbacks: { | |
| label: ctx => `${ctx.dataset.label}: ${ctx.raw}s (${(ctx.raw/60).toFixed(1)}m)` | |
| } | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| title: { display: true, text: 'Seconds' }, | |
| beginAtZero: true, | |
| }, | |
| x: { | |
| ticks: { maxRotation: 45, minRotation: 45 } | |
| } | |
| } | |
| } | |
| }); | |
| // --- Overhead bar chart --- | |
| new Chart(document.getElementById('overheadChart'), { | |
| type: 'bar', | |
| data: { | |
| labels: tools, | |
| datasets: [{ | |
| label: 'Pulsar overhead', | |
| data: overhead, | |
| backgroundColor: overhead.map(v => v >= 0 ? '#fca5a5' : '#86efac'), | |
| borderColor: overhead.map(v => v >= 0 ? red : green), | |
| borderWidth: 1, | |
| borderRadius: 4, | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { | |
| callbacks: { | |
| label: ctx => { | |
| const v = ctx.raw; | |
| const pct = batchTimes[ctx.dataIndex] > 0 | |
| ? ((v / batchTimes[ctx.dataIndex]) * 100).toFixed(0) | |
| : '---'; | |
| return `${v >= 0 ? '+' : ''}${v}s (${v >= 0 ? '+' : ''}${pct}%)`; | |
| } | |
| } | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| title: { display: true, text: 'Seconds' }, | |
| }, | |
| x: { | |
| ticks: { maxRotation: 45, minRotation: 45 } | |
| } | |
| } | |
| } | |
| }); | |
| // --- Cumulative line chart --- | |
| const batchCum = [], pulsarCum = []; | |
| batchTimes.reduce((acc, v, i) => { batchCum.push(acc + v); return acc + v; }, 0); | |
| pulsarTimes.reduce((acc, v, i) => { pulsarCum.push(acc + v); return acc + v; }, 0); | |
| new Chart(document.getElementById('cumulativeChart'), { | |
| type: 'line', | |
| data: { | |
| labels: tools, | |
| datasets: [ | |
| { | |
| label: 'GCP Batch', | |
| data: batchCum.map(v => +(v/60).toFixed(1)), | |
| borderColor: blue, | |
| backgroundColor: blueLight + '33', | |
| fill: true, | |
| tension: 0.3, | |
| pointRadius: 4, | |
| pointBackgroundColor: blue, | |
| }, | |
| { | |
| label: 'Pulsar', | |
| data: pulsarCum.map(v => +(v/60).toFixed(1)), | |
| borderColor: amber, | |
| backgroundColor: amberLight + '33', | |
| fill: true, | |
| tension: 0.3, | |
| pointRadius: 4, | |
| pointBackgroundColor: amber, | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| tooltip: { | |
| callbacks: { | |
| label: ctx => `${ctx.dataset.label}: ${ctx.raw} min` | |
| } | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| title: { display: true, text: 'Minutes' }, | |
| beginAtZero: true, | |
| }, | |
| x: { | |
| ticks: { maxRotation: 45, minRotation: 45 } | |
| } | |
| } | |
| } | |
| }); | |
| // --- Pie charts --- | |
| const pieColors = [ | |
| '#3b82f6', '#f59e0b', '#ef4444', '#22c55e', '#8b5cf6', '#ec4899', | |
| '#14b8a6', '#f97316', '#6366f1', '#84cc16', '#06b6d4', '#e11d48' | |
| ]; | |
| function makePie(canvasId, data, title) { | |
| new Chart(document.getElementById(canvasId), { | |
| type: 'doughnut', | |
| data: { | |
| labels: tools, | |
| datasets: [{ | |
| data: data, | |
| backgroundColor: pieColors, | |
| borderWidth: 2, | |
| borderColor: '#fff', | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| title: { display: true, text: title, font: { size: 13 } }, | |
| legend: { position: 'right', labels: { boxWidth: 12, padding: 8, font: { size: 10 } } }, | |
| tooltip: { | |
| callbacks: { | |
| label: ctx => { | |
| const total = ctx.dataset.data.reduce((a, b) => a + b, 0); | |
| const pct = ((ctx.raw / total) * 100).toFixed(1); | |
| return `${ctx.label}: ${ctx.raw}s (${pct}%)`; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| makePie('pieChartBatch', batchTimes, 'GCP Batch (19.2 min total)'); | |
| makePie('pieChartPulsar', pulsarTimes, 'Pulsar (30.5 min total)'); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment