<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IOPS</title><link>https://iops.gitlabpages.inria.fr/</link><description>Recent content on IOPS</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://iops.gitlabpages.inria.fr/index.xml" rel="self" type="application/rss+xml"/><item><title>Design Philosophy</title><link>https://iops.gitlabpages.inria.fr/about/philosophy/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/about/philosophy/</guid><description>&lt;p>Five design principles guide IOPS development. They explain why IOPS looks the way it does and what trade-offs were chosen.&lt;/p>
&lt;h2 id="1-one-yaml-to-rule-them-all">1. One YAML to Rule Them All&lt;/h2>
&lt;p>A single, self-contained specification captures the complete benchmark lifecycle: workload definition, parameter space, run rules, environment, and validation criteria. You should never need a second file, a sidecar script, or a hidden environment variable to understand what a study does. The YAML is the source of truth.&lt;/p></description></item><item><title>Execution Loop</title><link>https://iops.gitlabpages.inria.fr/developer-guide/execution-loop/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/developer-guide/execution-loop/</guid><description>&lt;p>This page documents the IOPS execution loop architecture: how the Runner, Planner, and Executor components orchestrate benchmark execution.&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>The execution loop follows a &lt;strong>pull-based&lt;/strong> design where the Runner requests tests from the Planner and delegates execution to the Executor:&lt;/p>
&lt;pre tabindex="0">&lt;code>┌─────────────────────────────────────────────────────────────────┐
│ IOPSRunner │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Planner │─────►│ Runner │─────►│ Executor │ │
│ │ │ │ Loop │ │ │ │
│ │ next_test() │ │ │ │ submit() │ │
│ │ │◄─────│ │◄─────│ wait_and_ │ │
│ │ record_ │ │ │ │ collect() │ │
│ │ completed() │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Matrix │ │ Cache │ │ Parser │ │
│ │ Builder │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
&lt;/code>&lt;/pre>&lt;h2 id="component-roles">Component Roles&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Component&lt;/th>
 &lt;th>Responsibility&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Runner&lt;/strong>&lt;/td>
 &lt;td>Orchestrates the execution loop, manages cache, tracks budget, writes results&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Planner&lt;/strong>&lt;/td>
 &lt;td>Generates test configurations, manages execution order, prepares artifacts&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Executor&lt;/strong>&lt;/td>
 &lt;td>Submits jobs, waits for completion, collects output and metrics&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Matrix Builder&lt;/strong>&lt;/td>
 &lt;td>Generates parameter combinations from config, applies constraints&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Cache&lt;/strong>&lt;/td>
 &lt;td>Stores/retrieves results to skip redundant executions&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Parser&lt;/strong>&lt;/td>
 &lt;td>Extracts metrics from benchmark output files&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="the-execution-loop">The Execution Loop&lt;/h2>
&lt;h3 id="main-loop-iopsrunnerrun">Main Loop (&lt;code>IOPSRunner.run()&lt;/code>)&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">run&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 1. Check budget&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> budget_exceeded:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 2. Get next test from planner&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>planner&lt;span style="color:#f92672">.&lt;/span>next_test()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> test &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">break&lt;/span> &lt;span style="color:#75715e"># No more tests&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 3. Check cache&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>cache &lt;span style="color:#f92672">and&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>use_cache_reads:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cached_result &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>cache&lt;span style="color:#f92672">.&lt;/span>get_cached_result(test&lt;span style="color:#f92672">.&lt;/span>vars, test&lt;span style="color:#f92672">.&lt;/span>repetition)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> cached_result:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Use cached result, skip execution&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test&lt;span style="color:#f92672">.&lt;/span>metadata&lt;span style="color:#f92672">.&lt;/span>update(cached_result)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> test&lt;span style="color:#f92672">.&lt;/span>metadata[&lt;span style="color:#e6db74">&amp;#39;__cached&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 4. Execute if not cached&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">not&lt;/span> used_cache:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>_write_status_file(test, status&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;RUNNING&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>executor&lt;span style="color:#f92672">.&lt;/span>submit(test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>executor&lt;span style="color:#f92672">.&lt;/span>wait_and_collect(test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Store in cache if succeeded&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> succeeded:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>cache&lt;span style="color:#f92672">.&lt;/span>store_result(&lt;span style="color:#f92672">...&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 5. Write status and results&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>_write_status_file(test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> save_test_execution(test)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 6. Notify planner of completion&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>planner&lt;span style="color:#f92672">.&lt;/span>record_completed_test(test)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="sequence-diagram">Sequence Diagram&lt;/h3>
&lt;pre tabindex="0">&lt;code>Runner Planner Executor Cache
 │ │ │ │
 │ next_test() │ │ │
 │───────────────────►│ │ │
 │ │ │ │
 │ ExecutionInstance │ │ │
 │◄───────────────────│ │ │
 │ │ │ │
 │ get_cached_result() │ │
 │──────────────────────────────────────────────────────────►│
 │ │ │ │
 │ (cache miss) │ │ │
 │◄──────────────────────────────────────────────────────────│
 │ │ │ │
 │ submit(test) │ │ │
 │────────────────────────────────────────►│ │
 │ │ │ │
 │ wait_and_collect(test) │ │
 │────────────────────────────────────────►│ │
 │ │ │ │
 │ test.metadata updated │ │
 │◄────────────────────────────────────────│ │
 │ │ │ │
 │ store_result() │ │ │
 │──────────────────────────────────────────────────────────►│
 │ │ │ │
 │ record_completed_test() │ │
 │───────────────────►│ │ │
 │ │ │ │
&lt;/code>&lt;/pre>&lt;h2 id="planner-architecture">Planner Architecture&lt;/h2>
&lt;h3 id="base-planner">Base Planner&lt;/h3>
&lt;p>All planners inherit from &lt;code>BasePlanner&lt;/code> and implement:&lt;/p></description></item><item><title>Installation</title><link>https://iops.gitlabpages.inria.fr/getting-started/installation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/getting-started/installation/</guid><description>&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#prerequisites">Prerequisites&lt;/a>&lt;/li>
&lt;li>&lt;a href="#quick-installation-from-pypi">Quick Installation (from PyPI)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#installation-from-source">Installation from Source&lt;/a>&lt;/li>
&lt;li>&lt;a href="#offline-installation-wheelhouse">Offline Installation (Wheelhouse)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#installation-with-spack-for-hpc-environments">Installation with Spack (for HPC environments)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#optional-dependencies">Optional Dependencies&lt;/a>&lt;/li>
&lt;li>&lt;a href="#verifying-your-installation">Verifying Your Installation&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;p>Before installing IOPS, ensure you have:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Python 3.10 or later&lt;/strong>&lt;/li>
&lt;li>For benchmark execution: Required tools in PATH (e.g., &lt;code>ior&lt;/code>, &lt;code>mpirun&lt;/code> for I/O benchmarks)&lt;/li>
&lt;li>For SLURM clusters: Access to a SLURM scheduler&lt;/li>
&lt;/ul>
&lt;h3 id="virtual-environment-recommended">Virtual environment (recommended)&lt;/h3>
&lt;p>Install IOPS into an isolated environment (&lt;code>venv&lt;/code> or Conda, both work the same) so its dependencies do not conflict with other Python tools on the machine.&lt;/p></description></item><item><title>IOPS YAML Format Reference</title><link>https://iops.gitlabpages.inria.fr/user-guide/yaml-schema/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/yaml-schema/</guid><description>&lt;p>&lt;em>Complete guide to the IOPS benchmark configuration format&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#file-structure-overview">File Structure Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#benchmark">&lt;code>benchmark&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#vars">&lt;code>vars&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#constraints-optional">&lt;code>constraints&lt;/code> (optional)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#command">&lt;code>command&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#scripts">&lt;code>scripts&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#output">&lt;code>output&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#reporting-optional">&lt;code>reporting&lt;/code> (optional)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#hooks-optional">&lt;code>hooks&lt;/code> (optional)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#machines-optional">&lt;code>machines&lt;/code> (optional)&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>See also: &lt;a href="../templating-and-context">Templating and Context Reference&lt;/a> for Jinja2 syntax, dynamic values, conditionals, and context variables.&lt;/p>
&lt;hr>
&lt;h2 id="file-structure-overview">File Structure Overview&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">benchmark&lt;/span>: &lt;span style="color:#75715e"># Global configuration (name, workdir, executor, search method)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">vars&lt;/span>: &lt;span style="color:#75715e"># Variable definitions (swept and derived)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">constraints&lt;/span>: &lt;span style="color:#75715e"># (Optional) Parameter validation rules&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">command&lt;/span>: &lt;span style="color:#75715e"># Command template and labels&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">scripts&lt;/span>: &lt;span style="color:#75715e"># Execution scripts and parsers&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">output&lt;/span>: &lt;span style="color:#75715e"># Output configuration (CSV, Parquet, SQLite)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">reporting&lt;/span>: &lt;span style="color:#75715e"># (Optional) Report generation settings&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">hooks&lt;/span>: &lt;span style="color:#75715e"># (Optional) Commands fired on run events&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">machines&lt;/span>: &lt;span style="color:#75715e"># (Optional) Per-machine config overrides&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Required sections:&lt;/strong> &lt;code>benchmark&lt;/code>, &lt;code>vars&lt;/code>, &lt;code>command&lt;/code>, &lt;code>scripts&lt;/code>, &lt;code>output&lt;/code>&lt;/p></description></item><item><title>Templating and Context Reference</title><link>https://iops.gitlabpages.inria.fr/user-guide/templating-and-context/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/templating-and-context/</guid><description>&lt;p>IOPS uses Jinja2 templating throughout the configuration to enable dynamic values, conditional logic, and computed expressions. This guide covers both the Jinja2 syntax and the context variables available in each configuration section.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#jinja2-syntax-reference">Jinja2 Syntax Reference&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#variable-substitution">Variable Substitution&lt;/a>&lt;/li>
&lt;li>&lt;a href="#conditionals">Conditionals&lt;/a>&lt;/li>
&lt;li>&lt;a href="#loops">Loops&lt;/a>&lt;/li>
&lt;li>&lt;a href="#filters">Filters&lt;/a>&lt;/li>
&lt;li>&lt;a href="#expressions">Expressions&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#context-by-configuration-section">Context by Configuration Section&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#commandtemplate">command.template&lt;/a>&lt;/li>
&lt;li>&lt;a href="#commandenv">command.env&lt;/a>&lt;/li>
&lt;li>&lt;a href="#scriptsscript_template">scripts[].script_template&lt;/a>&lt;/li>
&lt;li>&lt;a href="#scriptspostscript">scripts[].post.script&lt;/a>&lt;/li>
&lt;li>&lt;a href="#scriptsparserfile">scripts[].parser.file&lt;/a>&lt;/li>
&lt;li>&lt;a href="#scriptsparserparser_script">scripts[].parser.parser_script&lt;/a>&lt;/li>
&lt;li>&lt;a href="#varsexpr">vars[].expr&lt;/a>&lt;/li>
&lt;li>&lt;a href="#outputsinkpath">output.sink.path&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#complete-example">Complete Example&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;h3 id="template-support-by-field">Template Support by Field&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Field&lt;/th>
 &lt;th>Jinja2 Support&lt;/th>
 &lt;th>File Path Support&lt;/th>
 &lt;th>Inline Support&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>command.template&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>command.env&lt;/code> values&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>scripts[].script_template&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>scripts[].post.script&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>scripts[].parser.file&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>scripts[].parser.parser_script&lt;/code>&lt;/td>
 &lt;td>&lt;strong>No&lt;/strong>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>vars[].expr&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>output.sink.path&lt;/code>&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>hooks[].command&lt;/code>&lt;/td>
 &lt;td>Yes (own context, see &lt;a href="../hooks#command-context">Hooks&lt;/a>)&lt;/td>
 &lt;td>No&lt;/td>
 &lt;td>Yes&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="standard-context-variables">Standard Context Variables&lt;/h3>
&lt;p>These variables are available in all Jinja2 templates:&lt;/p></description></item><item><title>Contributing to IOPS</title><link>https://iops.gitlabpages.inria.fr/about/contributing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/about/contributing/</guid><description>&lt;p>Guidelines for contributing to IOPS.&lt;/p>
&lt;h2 id="getting-started">Getting Started&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Fork the Repository&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Clone your fork&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git clone https://gitlab.inria.fr/YOUR_USERNAME/iops.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd iops
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Set Up Development Environment&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Create virtual environment&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>python3 -m venv iops_env
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>source iops_env/bin/activate
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install in editable mode&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pip install -e .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install development dependencies&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pip install -r requirements-dev.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Verify the setup&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pytest tests/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="development-workflow">Development Workflow&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Create a Branch&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git checkout -b feature/my-new-feature
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># or&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git checkout -b fix/issue-description
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Make Changes&lt;/strong>&lt;/p></description></item><item><title>Probe System</title><link>https://iops.gitlabpages.inria.fr/developer-guide/probe-system/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/developer-guide/probe-system/</guid><description>&lt;p>This page documents the IOPS system probe mechanism for developers who want to extend the collected metrics or modify probe behavior.&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>The probe system collects system information (CPU, memory, filesystems, etc.) from the actual compute node where the benchmark runs. This matters for SLURM jobs, where the login node differs from compute nodes.&lt;/p>
&lt;p>&lt;strong>Key files:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>__iops_exit_handler.sh&lt;/code> - Centralized EXIT trap coordinator&lt;/li>
&lt;li>&lt;code>__iops_node_launcher.sh&lt;/code> - Resolves the job&amp;rsquo;s node list and starts one helper per node&lt;/li>
&lt;li>&lt;code>__iops_atexit_sysinfo.sh&lt;/code> - System info collection script&lt;/li>
&lt;li>&lt;code>__iops_sysinfo.json&lt;/code> - Collected data (generated at runtime)&lt;/li>
&lt;/ul>
&lt;h2 id="architecture">Architecture&lt;/h2>
&lt;pre tabindex="0">&lt;code>User Script Exit Handler Sysinfo Script
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ #!/bin/bash │ │ # Sets EXIT trap │ │ # Registers cleanup │
│ #SBATCH ... │ │ trap &amp;#39;_iops_run_ │ │ _iops_register_exit │
│ │ │ exit_actions&amp;#39; EXIT│ │ &amp;#34;_iops_collect_ │
│ # IOPS injection │ │ │ │ sysinfo&amp;#34; │
│ source handler.sh │───────►│ # Registry array │ │ │
│ source sysinfo.sh │───────────────────────────────────────►│ # Collection func │
│ │ │ _iops_register_exit │ │ _iops_collect_ │
│ # User&amp;#39;s benchmark │ │ () { ... } │ │ sysinfo() { ... } │
│ mpirun ./benchmark │ └─────────────────────┘ └─────────────────────┘
└─────────────────────┘ │
 │ │
 ▼ (on script exit) ▼
┌─────────────────────┐ ┌─────────────────────┐
│ __iops_sysinfo.json │◄───────│ _iops_run_exit_ │
│ { │ │ actions() calls │
│ &amp;#34;hostname&amp;#34;: ..., │ │ all registered │
│ &amp;#34;cpu_model&amp;#34;: ..., │ │ functions │
│ &amp;#34;duration&amp;#34;: ... │ └─────────────────────┘
│ } │
└─────────────────────┘
&lt;/code>&lt;/pre>&lt;p>The exit handler provides a centralized EXIT trap that other IOPS features (system probe, resource sampler) register cleanup actions with, avoiding trap conflicts.&lt;/p></description></item><item><title>Quick Start</title><link>https://iops.gitlabpages.inria.fr/getting-started/quickstart/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/getting-started/quickstart/</guid><description>&lt;p>Run your first benchmark with IOPS in a few minutes.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#step-1-create-a-configuration">Step 1: Create a Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-2-preview-your-benchmark">Step 2: Preview Your Benchmark&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-3-run-the-benchmark">Step 3: Run the Benchmark&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-4-explore-executions">Step 4: Explore Executions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-5-analyze-results">Step 5: Analyze Results&lt;/a>&lt;/li>
&lt;li>&lt;a href="#understanding-the-output">Understanding the Output&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="step-1-create-a-configuration">Step 1: Create a Configuration&lt;/h2>
&lt;p>Generate a YAML configuration template:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops generate my_config.yaml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Fully-documented template with all options&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iops generate my_config.yaml --full
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="step-2-preview-your-benchmark">Step 2: Preview Your Benchmark&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Dry-run to see what will be executed&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iops run my_config.yaml --dry-run
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iops run my_config.yaml -n
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Check configuration validity&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>iops check my_config.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The dry-run shows how many test instances will be generated, the parameter combinations, and estimated resource usage (for SLURM).&lt;/p></description></item><item><title>Writing Scripts</title><link>https://iops.gitlabpages.inria.fr/user-guide/writing-scripts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/writing-scripts/</guid><description>&lt;p>The &lt;code>scripts[]&lt;/code> section of your YAML tells IOPS &lt;em>how&lt;/em> to launch your benchmark. IOPS owns the parameter sweep and the result database; you own the shell script that actually runs the workload. This page is the practical guide to writing scripts; for the schema (every field, every option), see &lt;a href="../yaml-schema/#scripts">YAML Schema Reference / scripts&lt;/a>.&lt;/p>
&lt;hr>
&lt;h2 id="why-you-need-a-script-template">Why You Need a Script Template&lt;/h2>
&lt;p>IOPS does not assume anything about how your benchmark is launched: some benchmarks are a single executable, others need MPI launchers, module loads, environment setup, or pre-flight checks. The &lt;code>script_template&lt;/code> is the bridge: a Jinja2-rendered shell script that IOPS materializes to disk and hands to the configured executor. Per test execution, IOPS writes it to &lt;code>&amp;lt;execution_dir&amp;gt;/run_&amp;lt;name&amp;gt;.sh&lt;/code> so you can inspect (or rerun) exactly what was executed.&lt;/p></description></item><item><title>Writing Parsers</title><link>https://iops.gitlabpages.inria.fr/user-guide/writing-parsers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/writing-parsers/</guid><description>&lt;p>IOPS does not know the output format of your benchmark. Instead, you provide a small Python script (the &lt;strong>parser&lt;/strong>) that reads whatever your benchmark produces and returns the metrics you care about. This page explains how parsers work, how to write one, and where the extracted data ends up.&lt;/p>
&lt;hr>
&lt;h2 id="how-it-works">How It Works&lt;/h2>
&lt;p>Every benchmark writes its results differently: JSON files, plain text logs, CSV tables, binary formats. The parser is the bridge between raw benchmark output and structured data that IOPS can store, plot, and analyze. After each execution completes, IOPS runs it through this sequence:&lt;/p></description></item><item><title>Data Sources</title><link>https://iops.gitlabpages.inria.fr/developer-guide/data-sources/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/developer-guide/data-sources/</guid><description>&lt;p>This page documents where IOPS CLI commands read their data.&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>IOPS commands read from result files and from metadata files generated during execution. Metadata files have the &lt;code>__iops_&lt;/code> prefix and are documented in the &lt;a href="https://iops.gitlabpages.inria.fr/user-guide/metadata-files/">Metadata Files&lt;/a> user guide.&lt;/p>
&lt;h2 id="data-sources-by-command">Data Sources by Command&lt;/h2>
&lt;pre tabindex="0">&lt;code>iops find ◄─── __iops_index.json
 ◄─── __iops_status_rollup.json (preferred; falls back to per-folder scan)
 ◄─── __iops_status.json (fallback)
 ◄─── __iops_run_metadata.json (optional metadata)

iops find --watch ◄─── __iops_index.json (polling)
 ◄─── __iops_status_rollup.json (preferred; polling)
 ◄─── __iops_status.json (fallback scan)

iops report ◄─── __iops_run_metadata.json
 ◄─── results.csv/parquet/sqlite
&lt;/code>&lt;/pre>&lt;h3 id="status-roll-up-fast-path">Status roll-up fast path&lt;/h3>
&lt;p>Both &lt;code>iops find&lt;/code> and watch mode read the run-root &lt;code>__iops_status_rollup.json&lt;/code>
aggregate instead of scanning every execution&amp;rsquo;s repetition folders, which keeps
them responsive on large parameter spaces. The per-folder &lt;code>__iops_status.json&lt;/code>
files remain the source of truth (and the node-to-runner channel in
single-allocation mode); the roll-up is an accelerator. Watch always prefers it;
&lt;code>iops find&lt;/code> only trusts it when the run marked it &lt;code>complete&lt;/code>, otherwise it falls
back to the folder scan. When the roll-up is absent (older runs, archives), both
fall back automatically.&lt;/p></description></item><item><title>Deprecations</title><link>https://iops.gitlabpages.inria.fr/about/deprecations/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/about/deprecations/</guid><description>&lt;p>This page lists deprecated features and provides migration guidance.&lt;/p>
&lt;h2 id="backwards-compatibility-policy">Backwards Compatibility Policy&lt;/h2>
&lt;p>IOPS follows a &lt;strong>2 minor version deprecation cycle&lt;/strong>:&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Version&lt;/th>
 &lt;th>Behavior&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>N&lt;/td>
 &lt;td>Feature deprecated with warning, old behavior still works&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>N+1&lt;/td>
 &lt;td>Warning remains, old behavior still works&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>N+2&lt;/td>
 &lt;td>Feature removed, error with migration guidance&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>This gives users at least two releases to migrate their configurations.&lt;/p>
&lt;h2 id="current-deprecations">Current Deprecations&lt;/h2>
&lt;h3 id="benchmarkexecutor_options--benchmarkslurm_options">&lt;code>benchmark.executor_options&lt;/code> → &lt;code>benchmark.slurm_options&lt;/code>&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>&lt;/th>
 &lt;th>&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Deprecated in&lt;/strong>&lt;/td>
 &lt;td>3.5.0&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Remove after&lt;/strong>&lt;/td>
 &lt;td>3.7.0&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Reason&lt;/strong>&lt;/td>
 &lt;td>Renamed for clarity (options are SLURM-specific)&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Before:&lt;/strong>&lt;/p></description></item><item><title>Execution Matrix Generation</title><link>https://iops.gitlabpages.inria.fr/user-guide/matrix-generation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/matrix-generation/</guid><description>&lt;p>&lt;em>How IOPS builds the parameter space from your variable definitions&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>IOPS generates an &lt;strong>execution matrix&lt;/strong> from your variable definitions; each row is a unique parameter combination to test.&lt;/p>
&lt;pre tabindex="0">&lt;code>vars: Execution Matrix:
 nodes: [1, 2] → | nodes | threads |
 threads: [4, 8] | 1 | 4 |
 | 1 | 8 |
 | 2 | 4 |
 | 2 | 8 |
&lt;/code>&lt;/pre>&lt;hr>
&lt;h2 id="variable-types">Variable Types&lt;/h2>
&lt;h3 id="swept-variables">Swept Variables&lt;/h3>
&lt;p>Variables with &lt;code>sweep&lt;/code> define the parameter space. IOPS creates a &lt;strong>Cartesian product&lt;/strong> of all swept variables.&lt;/p></description></item><item><title>IOPS Studio</title><link>https://iops.gitlabpages.inria.fr/getting-started/studio/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/getting-started/studio/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Experimental Feature&lt;/strong>&lt;/p>
&lt;p>IOPS Studio is experimental. It may contain bugs or undergo breaking changes in future releases. Please report any issues you encounter.&lt;/p>
&lt;/blockquote>
&lt;p>IOPS Studio is a local web UI for driving IOPS end to end: connect to a target (your laptop or an HPC cluster over SSH), build a benchmark config from a form, run it, and view the results, all from your browser.&lt;/p>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Studio runs as a small local web server and does its work through &lt;strong>one interactive shell per target&lt;/strong>, the same terminal you see on the right of the screen. Connecting to a cluster is just an &lt;code>ssh&lt;/code> session inside that terminal, so everything works exactly as it would by hand, and if a command fails you are already in the right shell to fix it. Because it uses the one already-authenticated channel, Studio also works on locked-down login nodes that only accept an interactive password or 2FA.&lt;/p></description></item><item><title>Search Methods</title><link>https://iops.gitlabpages.inria.fr/user-guide/search-methods/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/search-methods/</guid><description>&lt;p>IOPS supports multiple search strategies for exploring your parameter space, each with different trade-offs between thoroughness and efficiency.&lt;/p>
&lt;h2 id="exhaustive-search">Exhaustive Search&lt;/h2>
&lt;p>Tests all possible parameter combinations. Thorough but expensive for large parameter spaces.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">benchmark&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">search_method&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;exhaustive&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="bayesian-optimization">Bayesian Optimization&lt;/h2>
&lt;p>Uses a surrogate model to explore the parameter space, focusing on promising regions. Default parameters are empirically tuned: with 20 iterations (~7% of search space), Bayesian optimization achieves ~90% of optimal vs ~79% for random search.&lt;/p></description></item><item><title>Bayesian Optimization</title><link>https://iops.gitlabpages.inria.fr/user-guide/bayesian-optimization/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/bayesian-optimization/</guid><description>&lt;p>Bayesian optimization lets IOPS explore a parameter space to find configurations that maximize or minimize a target metric, using far fewer evaluations than exhaustive search. Instead of testing every combination, it builds a surrogate model from observed results and focuses on the most promising regions.&lt;/p>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#basic-configuration">Basic Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#how-iops-implements-bayesian-optimization">How IOPS Implements Bayesian Optimization&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#the-optimization-loop">The optimization loop&lt;/a>&lt;/li>
&lt;li>&lt;a href="#parameter-space-encoding">Parameter space encoding&lt;/a>&lt;/li>
&lt;li>&lt;a href="#nearest-neighbor-mapping">Nearest-neighbor mapping&lt;/a>&lt;/li>
&lt;li>&lt;a href="#feedback-and-metric-aggregation">Feedback and metric aggregation&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#choosing-a-surrogate-model">Choosing a Surrogate Model&lt;/a>&lt;/li>
&lt;li>&lt;a href="#acquisition-functions">Acquisition Functions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#convergence-and-early-stopping">Convergence and Early Stopping&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#default-behavior">Default behavior&lt;/a>&lt;/li>
&lt;li>&lt;a href="#early-stopping-with-xi-boost">Early stopping with xi boost&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#combining-with-exhaustive-variables">Combining with Exhaustive Variables&lt;/a>&lt;/li>
&lt;li>&lt;a href="#fallback-to-exhaustive">Fallback to Exhaustive&lt;/a>&lt;/li>
&lt;li>&lt;a href="#configuration-reference">Configuration Reference&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Bayesian optimization (BO) is a sequential, model-based strategy for optimizing expensive black-box functions. In benchmarking, each &amp;ldquo;function evaluation&amp;rdquo; is a full benchmark run that may take minutes or hours; BO reduces the number of runs needed by learning from previous results. Use it when runs are expensive and you want the best configuration without testing every combination. Prefer exhaustive or random search when the space is small enough to cover fully, when you need complete coverage for statistical analysis, or when there is no clear optimization target.&lt;/p></description></item><item><title>Changelog</title><link>https://iops.gitlabpages.inria.fr/about/changelog/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/about/changelog/</guid><description>&lt;p>All notable changes to IOPS are documented here.&lt;/p>
&lt;h2 id="3510---2026-10-26">[3.5.10] - 2026-10-26&lt;/h2>
&lt;h3 id="added">Added&lt;/h3>
&lt;ul>
&lt;li>&lt;code>iops deps&lt;/code> lists the optional dependencies, what each one enables, and whether it is installed, so the packages behind Bayesian search, Parquet sinks, watch mode, plot export, gallery downscaling, and Studio can be checked in one place instead of by triggering the feature and reading the error. &lt;code>--missing&lt;/code> narrows the table to what is absent, and &lt;code>--check&lt;/code> exits with status 1 when anything is missing, for use in CI. The same catalog now backs the availability flags inside IOPS: &lt;code>iops/deps.py&lt;/code> holds one entry per extra and the modules that gate on a package call &lt;code>is_available()&lt;/code> instead of carrying their own try/except, so the command and the runtime cannot disagree about what is installed. Detection uses each package&amp;rsquo;s import name rather than its distribution name, which is why Pillow (imported as &lt;code>PIL&lt;/code>) is reported correctly. Every install hint IOPS prints now quotes the package spec (&lt;code>pip install &amp;quot;iops-benchmark[bayesian]&amp;quot;&lt;/code>), because zsh treats the square brackets of an extra as a glob and rejects the unquoted form with &lt;code>no matches found&lt;/code>. A test asserts no source file emits an unquoted hint.&lt;/li>
&lt;li>&lt;code>xaxis_range&lt;/code> and &lt;code>yaxis_range&lt;/code> set explicit limits on a report plot&amp;rsquo;s axes, as &lt;code>[min, max]&lt;/code>, instead of leaving the zoom to Plotly. Both are written in data units even when the axis is logarithmic (&lt;code>yaxis_range: [1e-6, 1e-2]&lt;/code> with &lt;code>log_y: true&lt;/code> shows 10^-6 to 10^-2), since the conversion to powers of ten happens at render time. This makes several plots directly comparable on identical axes, lets a bar chart be pinned to zero, and allows zooming into a band of interest. The bounds must be numbers with min below max, and on a logarithmic axis the lower bound must be positive, because such an axis cannot represent zero. When a range is set, the decade tick rule reads the range rather than the data. The options are also available in the Studio plot editor.&lt;/li>
&lt;li>&lt;strong>Hooks&lt;/strong> run a command of your choice when something happens during a run, which makes a long run on a cluster easy to follow without watching the terminal. A top-level &lt;code>hooks:&lt;/code> list names an &lt;code>event&lt;/code> (&lt;code>start&lt;/code>, &lt;code>progress&lt;/code>, &lt;code>failures&lt;/code>, &lt;code>consecutive_failures&lt;/code>, or &lt;code>finish&lt;/code>), says when it fires with &lt;code>at&lt;/code> (one-shot thresholds) and/or &lt;code>every&lt;/code> (a repeating threshold), and gives a &lt;code>command&lt;/code>. Thresholds are an integer count or a percentage string such as &lt;code>&amp;quot;25%&amp;quot;&lt;/code>, counted in attempts (executions times repetitions). A &lt;code>failures&lt;/code> percentage is the failure rate among tests that ran and is held back until &lt;code>min_tests&lt;/code> have run (default 10, lowered on small runs), &lt;code>consecutive_failures&lt;/code> re-arms after a success, and &lt;code>finish&lt;/code> hooks can be restricted with &lt;code>when&lt;/code> to the outcomes &lt;code>completed&lt;/code>, &lt;code>interrupted&lt;/code>, &lt;code>budget_exceeded&lt;/code>, &lt;code>fail_fast&lt;/code>, or &lt;code>crashed&lt;/code>. The command is a Jinja2 template rendered against the run context (&lt;code>progress&lt;/code>, &lt;code>run&lt;/code>, &lt;code>benchmark.name&lt;/code>, &lt;code>workdir&lt;/code>, &lt;code>report_path&lt;/code>, &lt;code>hostname&lt;/code>, &lt;code>os_env&lt;/code>, and a ready-made &lt;code>message&lt;/code>), with a &lt;code>quote&lt;/code> filter for the shell, and the same values are exported as &lt;code>IOPS_*&lt;/code> environment variables for scripts. Commands run with bash on the host running IOPS, in the background and in order, each in its own session with a &lt;code>timeout&lt;/code> (default 60 seconds), so a slow or failing hook is logged as a warning and never affects the run, and &lt;code>finish&lt;/code> waits for pending hooks before IOPS exits. Output goes to &lt;code>__iops_hooks.log&lt;/code> in the run directory. Hooks are listed but not fired by &lt;code>--dry-run&lt;/code>, &lt;code>iops run --no-hooks&lt;/code> disables them, and &lt;code>hooks&lt;/code> can be overridden per entry under &lt;code>machines&lt;/code>. &lt;code>iops check&lt;/code> renders every hook command against a sample context to catch misspelled names, and &lt;code>iops hooks test config.yaml&lt;/code> (with &lt;code>--hook&lt;/code>, &lt;code>--event&lt;/code>, &lt;code>--render-only&lt;/code>, and &lt;code>--machine&lt;/code>) runs the commands once so a mail command can be tried before a long run. A bare &lt;code>on:&lt;/code> key is rejected with a pointer to &lt;code>event:&lt;/code>, because YAML reads it as a boolean. See the &lt;a href="https://iops.gitlabpages.inria.fr/iops/user-guide/hooks/">Hooks guide&lt;/a> and &lt;code>examples/hooks/hooks.yaml&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="changed">Changed&lt;/h3>
&lt;ul>
&lt;li>Every IOPS command starts about twice as fast. Deciding whether an optional package was installed was done by importing it, and importing scikit-optimize pulls in scikit-learn and SciPy behind it, which cost roughly 0.8 seconds. That was paid on every invocation to set a boolean, including &lt;code>iops --version&lt;/code> and &lt;code>iops find&lt;/code>, whether or not the run used Bayesian search. The availability gates now locate a module instead of executing it, which measures as no cost at all, taking startup from about 1.0 to about 0.45 seconds; the remainder is pandas, reached through the results writer. &lt;code>iops deps&lt;/code> still performs a real import, since there the answer is the product and a package that is installed but broken should be reported as missing. This became a one-line change because the gates had already moved behind a single catalog.&lt;/li>
&lt;li>A logarithmic axis (&lt;code>log_x&lt;/code>, &lt;code>log_y&lt;/code>) whose data spans two decades or more is now labelled once per decade, as a power of ten. Plotly labels minor log ticks by default, so an axis covering four decades read &lt;code>2, 0.001, 5, 2, 100µ, 5, 2, 10µ&lt;/code>, mixing minor labels in with the decade marks. Data confined to less than two decades keeps the automatic ticks: those labels are ordinary numbers, and a decade rule would leave such an axis with a single label or none at all, since its whole range can sit between two powers of ten.&lt;/li>
&lt;/ul>
&lt;h3 id="fixed">Fixed&lt;/h3>
&lt;ul>
&lt;li>The &lt;code>report_config.yaml&lt;/code> written after a run dropped most plot options: only &lt;code>type&lt;/code>, &lt;code>x_var&lt;/code>, &lt;code>y_var&lt;/code>, &lt;code>group_by&lt;/code>, &lt;code>title&lt;/code>, and &lt;code>colorscale&lt;/code> were kept, so &lt;code>log_x&lt;/code>, &lt;code>log_y&lt;/code>, axis labels, sizes, error-bar and outlier toggles, &lt;code>z_metric&lt;/code>, &lt;code>color_by&lt;/code>, &lt;code>size_by&lt;/code>, and the coverage heatmap options were lost. Because &lt;code>iops report&lt;/code> auto-loads that file in preference to the run metadata, a regenerated report silently ignored them, even though the report produced at the end of the run honored them. Every non-default plot field is now written, for both &lt;code>reporting.metrics&lt;/code> plots and &lt;code>reporting.default_plots&lt;/code>. Runs created with an earlier version keep the truncated file; restore the missing options by hand, or delete the file so &lt;code>iops report&lt;/code> falls back to the configuration stored in the run metadata.&lt;/li>
&lt;li>Bar plot value labels used two decimal places, so metrics below 0.005 were all labeled &lt;code>0.00&lt;/code>. Labels now show three significant digits (&lt;code>0.000956&lt;/code>).&lt;/li>
&lt;li>With &lt;code>fail_fast&lt;/code> enabled, the core-hours of the test that stopped the run were not added to the total, because the run stopped before they were counted. They are now included, so the budget total and hooks see the full cost.&lt;/li>
&lt;li>The automatic HTML report produced at the end of a run now includes resource sampling data. It was generated before the run metadata was finalized and &lt;code>__iops_resource_summary.csv&lt;/code> was written, so custom plots on resource metrics (such as &lt;code>gpu_avg_utilization_pct&lt;/code>) reported &amp;ldquo;not found in results&amp;rdquo;, and the Resource Sampling table, System Environment section, and total runtime were missing until the report was regenerated with &lt;code>iops report&lt;/code>. The report is now built as the last step of a run that completes normally.&lt;/li>
&lt;li>The version probe wrote an &lt;code>__iops_versions.json&lt;/code> that no JSON parser could read whenever a version command printed more than one line on macOS or BSD, or printed a tab on any system. Its escaping relied on a GNU-only &lt;code>sed&lt;/code> idiom and a tab substitution that never took effect. Escaping now uses bash parameter expansion and produces identical, valid output on bash 3.2 and 5.x.&lt;/li>
&lt;li>Studio no longer hangs on targets whose terminal truncates long input lines. Every command was sent as a single line, so one longer than the target tty&amp;rsquo;s input buffer was cut short and the shell waited forever for the missing newline, leaving the session unusable. Environment discovery hit this on clusters such as PLAFRIM, which truncate at 1024 bytes. Commands that would exceed the budget are now streamed to a temporary script on the target and invoked with a short line (sourced, so &lt;code>module load&lt;/code> and &lt;code>export&lt;/code> still persist). Streamed transfers to macOS and BSD targets also work now: they decoded with &lt;code>base64 -d &amp;quot;$file&amp;quot;&lt;/code>, which BSD &lt;code>base64&lt;/code> rejects.&lt;/li>
&lt;/ul>
&lt;h2 id="359---2026-08-19">[3.5.9] - 2026-08-19&lt;/h2>
&lt;h3 id="added-1">Added&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>IOPS Studio&lt;/strong> (experimental): a local web UI for using IOPS end to end, launched with &lt;code>iops studio&lt;/code> (requires the optional &lt;code>nicegui&lt;/code> dependency). See the &lt;a href="https://iops.gitlabpages.inria.fr/iops/getting-started/studio/">Getting Started guide&lt;/a>.&lt;/p></description></item><item><title>Adaptive Variables</title><link>https://iops.gitlabpages.inria.fr/user-guide/adaptive-variables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/adaptive-variables/</guid><description>&lt;p>Adaptive variables let IOPS probe for threshold values by starting at an initial value and progressing until a stop condition is met. Use them to find limits such as maximum problem sizes, memory capacities, or performance cliffs without manually defining the search space.&lt;/p>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#basic-configuration">Basic Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#step-methods">Step Methods&lt;/a>&lt;/li>
&lt;li>&lt;a href="#stop-conditions">Stop Conditions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#direction">Direction&lt;/a>&lt;/li>
&lt;li>&lt;a href="#safety-limits">Safety Limits&lt;/a>&lt;/li>
&lt;li>&lt;a href="#probe-results">Probe Results&lt;/a>&lt;/li>
&lt;li>&lt;a href="#multiple-repetitions">Multiple Repetitions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#combining-with-swept-variables">Combining with Swept Variables&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#how-probes-advance">How probes advance&lt;/a>&lt;/li>
&lt;li>&lt;a href="#multiple-swept-variables">Multiple swept variables&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#staircase-search-with-an-escalating-variable">Staircase Search with an Escalating Variable&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#how-the-staircase-walks">How the staircase walks&lt;/a>&lt;/li>
&lt;li>&lt;a href="#reading-the-frontier">Reading the frontier&lt;/a>&lt;/li>
&lt;li>&lt;a href="#when-not-to-use-it">When not to use it&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#complete-example">Complete Example&lt;/a>&lt;/li>
&lt;li>&lt;a href="#constraints">Constraints&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#why-only-one-adaptive-variable">Why only one adaptive variable?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#adaptive-vs-bayesian-optimization">Adaptive vs Bayesian optimization&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#configuration-reference">Configuration Reference&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Unlike swept variables (which test a fixed list of values) or derived variables (which are computed from other variables), an adaptive variable is driven by a feedback loop:&lt;/p></description></item><item><title>Caching</title><link>https://iops.gitlabpages.inria.fr/user-guide/caching/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/caching/</guid><description>&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#configuration">Configuration&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#basic-usage">Basic Usage&lt;/a>&lt;/li>
&lt;li>&lt;a href="#cache-only-mode">Cache-Only Mode&lt;/a>&lt;/li>
&lt;li>&lt;a href="#parameter-hashing">Parameter Hashing&lt;/a>&lt;/li>
&lt;li>&lt;a href="#excluding-variables-from-cache">Excluding Variables from Cache&lt;/a>&lt;/li>
&lt;li>&lt;a href="#cache-schema">Cache Schema&lt;/a>&lt;/li>
&lt;li>&lt;a href="#important-command-changes-are-not-detected">Important: Command Changes Are Not Detected&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-gets-cached">What Gets Cached&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#inspecting-the-cache">Inspecting the Cache&lt;/a>&lt;/li>
&lt;li>&lt;a href="#rebuilding-the-cache">Rebuilding the Cache&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>IOPS caches execution results in SQLite so that tests with identical parameters can be skipped on re-runs. This speeds up iterative development, lets you safely retry failed runs without re-executing successful tests, and handles repetitions correctly (each repetition is cached separately).&lt;/p></description></item><item><title>License</title><link>https://iops.gitlabpages.inria.fr/about/license/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/about/license/</guid><description>&lt;p>IOPS is licensed under the BSD 3-Clause License.&lt;/p>
&lt;h2 id="bsd-3-clause-license">BSD 3-Clause License&lt;/h2>
&lt;p>Copyright (c) 2024-2025, TADaaM - INRIA Bordeaux
All rights reserved.&lt;/p>
&lt;p>Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.&lt;/p></description></item><item><title>Machine Overrides</title><link>https://iops.gitlabpages.inria.fr/user-guide/machines/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/machines/</guid><description>&lt;p>Use a single configuration file across multiple systems with per-machine overrides.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#merge-rules">Merge Rules&lt;/a>&lt;/li>
&lt;li>&lt;a href="#configuration">Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#example-local-development--hpc-cluster">Example: Local Development + HPC Cluster&lt;/a>&lt;/li>
&lt;li>&lt;a href="#use-cases">Use Cases&lt;/a>&lt;/li>
&lt;li>&lt;a href="#tips">Tips&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>The &lt;code>machines&lt;/code> section lets you define per-machine overrides inside a single YAML file. Instead of maintaining separate configs for your laptop, HPC clusters, or cloud instances, write one base config and override only what differs per environment.&lt;/p>
&lt;p>Select the target machine at runtime:&lt;/p></description></item><item><title>Execution Backends</title><link>https://iops.gitlabpages.inria.fr/user-guide/execution-backends/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/execution-backends/</guid><description>&lt;p>IOPS supports two execution backends for running benchmarks: local execution and SLURM cluster submission.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#local-executor">Local Executor&lt;/a>&lt;/li>
&lt;li>&lt;a href="#parallel-execution">Parallel Execution&lt;/a>&lt;/li>
&lt;li>&lt;a href="#slurm-executor">SLURM Executor&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#budget-control">Budget Control&lt;/a> → &lt;a href="../budget-control">dedicated guide&lt;/a>&lt;/li>
&lt;li>&lt;a href="#job-monitoring">Job Monitoring&lt;/a>&lt;/li>
&lt;li>&lt;a href="#custom-slurm-commands">Custom SLURM Commands&lt;/a>&lt;/li>
&lt;li>&lt;a href="#single-allocation-mode">Single-Allocation Mode&lt;/a> → &lt;a href="../single-allocation-mode">dedicated guide&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="local-executor">Local Executor&lt;/h2>
&lt;p>Runs benchmarks directly on your local machine using subprocesses. Suited for development, small experiments, single-node benchmarks, and quick iterations.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">benchmark&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;Local Test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">workdir&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;./workdir&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">executor&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;local&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">scripts&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;benchmark&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script_template&lt;/span>: |&lt;span style="color:#e6db74">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> #!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> set -euo pipefail
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> {{ command.template }}&lt;/span> 
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="parallel-execution">Parallel Execution&lt;/h2>
&lt;p>By default, IOPS runs tests sequentially (one at a time). Run multiple tests concurrently by setting the &lt;code>parallel&lt;/code> option:&lt;/p></description></item><item><title>Running IO500 with IOPS</title><link>https://iops.gitlabpages.inria.fr/user-guide/io500/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/io500/</guid><description>&lt;hr>
&lt;h2 id="running-the-io500-benchmark-with-iops">Running the IO500 Benchmark with IOPS&lt;/h2>
&lt;p>The configuration generated by:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops generate --io500
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>[!NOTE]
This is a &lt;strong>ready-to-use template&lt;/strong>, but it should not be used as-is. Before running any benchmark, make sure to review and adjust the configuration to match your HPC system, storage setup, and testing goals.&lt;/p>
&lt;p>The generated YAML file includes comments that explain each parameter and how to customize it. It is strongly recommended to read them before starting the benchmark.&lt;/p></description></item><item><title>Single-Allocation Mode</title><link>https://iops.gitlabpages.inria.fr/user-guide/single-allocation-mode/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/single-allocation-mode/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Experimental Feature&lt;/strong>&lt;/p>
&lt;p>Single-allocation mode is experimental. It may contain bugs or undergo breaking changes in future releases. Please report any issues you encounter.&lt;/p>
&lt;/blockquote>
&lt;p>Single-allocation mode runs all tests within a single SLURM allocation instead of submitting separate jobs for each test.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#when-to-use">When to Use&lt;/a>&lt;/li>
&lt;li>&lt;a href="#basic-configuration">Basic Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#how-it-works">How It Works&lt;/a>&lt;/li>
&lt;li>&lt;a href="#running-mpi-programs">Running MPI Programs&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>By default, IOPS submits a separate SLURM job for each test (&lt;code>per-test&lt;/code> mode). Single-allocation mode changes this behavior:&lt;/p></description></item><item><title>Budget Control</title><link>https://iops.gitlabpages.inria.fr/user-guide/budget-control/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/budget-control/</guid><description>&lt;p>IOPS can track core-hours consumption and automatically stop execution when a budget limit is reached, helping prevent exceeding compute allocations on HPC systems.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#configuration">Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#how-core-hours-are-calculated">How Core-Hours Are Calculated&lt;/a>&lt;/li>
&lt;li>&lt;a href="#duration-source-and-accuracy">Duration Source and Accuracy&lt;/a>&lt;/li>
&lt;li>&lt;a href="#budget-enforcement-behavior">Budget Enforcement Behavior&lt;/a>&lt;/li>
&lt;li>&lt;a href="#dry-run-estimation">Dry-Run Estimation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#cache-interaction">Cache Interaction&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>Set a budget limit in your YAML config:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">benchmark&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">max_core_hours&lt;/span>: &lt;span style="color:#ae81ff">1000&lt;/span> &lt;span style="color:#75715e"># Stop after 1000 core-hours&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cores_expr&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;{{ nodes * processes_per_node }}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or from command line (overrides config value):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops run config.yaml --max-core-hours &lt;span style="color:#ae81ff">1000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Option&lt;/th>
 &lt;th>Description&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>max_core_hours&lt;/code>&lt;/td>
 &lt;td>Maximum core-hours budget. Execution stops when reached.&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>cores_expr&lt;/code>&lt;/td>
 &lt;td>Jinja2 expression to compute cores per test. Defaults to &lt;code>1&lt;/code> if not set.&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="how-core-hours-are-calculated">How Core-Hours Are Calculated&lt;/h2>
&lt;pre tabindex="0">&lt;code>core_hours = cores × (duration_seconds / 3600)
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>&lt;strong>&lt;code>cores&lt;/code>&lt;/strong>: Evaluated from &lt;code>cores_expr&lt;/code> using test variables&lt;/li>
&lt;li>&lt;strong>&lt;code>duration_seconds&lt;/code>&lt;/strong>: Actual script execution time (see &lt;a href="#duration-source-and-accuracy">Duration Source&lt;/a>)&lt;/li>
&lt;/ul>
&lt;h3 id="setting-cores_expr-correctly">Setting cores_expr Correctly&lt;/h3>
&lt;p>The &lt;code>cores_expr&lt;/code> should reflect the cores you&amp;rsquo;re actually charged for on your HPC system:&lt;/p></description></item><item><title>Hooks</title><link>https://iops.gitlabpages.inria.fr/user-guide/hooks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/hooks/</guid><description>&lt;p>Hooks run a command of your choice when something happens during a run: the run starts, a share of the tests has concluded, too many tests are failing, or the run ends. The typical use is monitoring a long run on a cluster, for example sending a mail when the run is half done and another when it finishes. They are equally useful for housekeeping, such as copying the results somewhere once the run is over.&lt;/p></description></item><item><title>Resource Sampling</title><link>https://iops.gitlabpages.inria.fr/user-guide/resource-tracing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/resource-tracing/</guid><description>&lt;p>IOPS can optionally sample CPU, memory, GPU, and I/O activity during benchmark execution, so you can correlate parameter configurations with resource footprint (e.g., heatmap analysis of how parameters affect resource usage).&lt;/p>
&lt;p>&lt;strong>Warning: Performance Impact&lt;/strong>&lt;/p>
&lt;p>Resource sampling runs background processes that periodically collect system metrics and write to CSV files. Although the samplers run at low priority (via &lt;code>renice&lt;/code>), they may still affect benchmark results: a small amount of CPU time for reading proc files, an &lt;code>nvidia-smi&lt;/code> invocation per GPU sample, one CSV append per sample interval, and ~1-2 MB of memory per process. For performance-critical measurements, run your benchmark &lt;strong>without sampling first&lt;/strong> to establish a baseline, then enable sampling in a separate run to collect resource data.&lt;/p></description></item><item><title>Metadata Files</title><link>https://iops.gitlabpages.inria.fr/user-guide/metadata-files/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/metadata-files/</guid><description>&lt;p>IOPS generates metadata files with the &lt;code>__iops_&lt;/code> prefix to track executions, enable fast lookups, and support report generation. This page documents each file, when it is written, and how to control its generation.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#io-overhead-considerations">I/O Overhead Considerations&lt;/a>&lt;/li>
&lt;li>&lt;a href="#metadata-files-overview">Metadata Files Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#controlling-metadata-generation">Controlling Metadata Generation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#file-reference">File Reference&lt;/a>&lt;/li>
&lt;li>&lt;a href="#best-practices">Best Practices&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="io-overhead-considerations">I/O Overhead Considerations&lt;/h2>
&lt;p>Metadata files are written to the &lt;strong>workdir&lt;/strong> during benchmark execution. They are small (typically a few KB each), but consider disabling them when:&lt;/p></description></item><item><title>Exploring Executions</title><link>https://iops.gitlabpages.inria.fr/user-guide/exploring-executions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/exploring-executions/</guid><description>&lt;p>IOPS provides tools to explore, search, and inspect execution folders after running benchmarks, useful for large parameter sweeps where you need to find specific configurations or investigate failures. For the full flag reference, see the &lt;a href="../cli#find---explore-executions">CLI guide&lt;/a>.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#finding-executions">Finding Executions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#filtering-executions">Filtering Executions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advanced-options">Advanced Options&lt;/a>&lt;/li>
&lt;li>&lt;a href="#watch-mode">Watch Mode&lt;/a>&lt;/li>
&lt;li>&lt;a href="#iops-metadata-files">IOPS Metadata Files&lt;/a>&lt;/li>
&lt;li>&lt;a href="#inspecting-archives">Inspecting Archives&lt;/a>&lt;/li>
&lt;li>&lt;a href="#archiving-workdirs">Archiving Workdirs&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="finding-executions">Finding Executions&lt;/h2>
&lt;p>Use the &lt;code>find&lt;/code> command to locate and display execution folders with their parameters:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops find /path/to/workdir
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>find&lt;/code> command works with four types of paths:&lt;/p></description></item><item><title>Command Line Interface</title><link>https://iops.gitlabpages.inria.fr/user-guide/cli/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/cli/</guid><description>&lt;p>Complete reference for the IOPS command-line interface.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#overview">Overview&lt;/a>&lt;/li>
&lt;li>&lt;a href="#basic-usage">Basic Usage&lt;/a>&lt;/li>
&lt;li>&lt;a href="#subcommand-reference">Subcommand Reference&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>IOPS uses a subcommand-based CLI similar to git and docker. Each subcommand has its own options and help.&lt;/p>
&lt;h2 id="basic-usage">Basic Usage&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops &amp;lt;subcommand&amp;gt; &lt;span style="color:#f92672">[&lt;/span>arguments&lt;span style="color:#f92672">]&lt;/span> &lt;span style="color:#f92672">[&lt;/span>options&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Command&lt;/th>
 &lt;th>Purpose&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>iops run &amp;lt;config.yaml&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Execute benchmark&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops check &amp;lt;config.yaml&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Validate configuration&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops generate [path]&lt;/code>&lt;/td>
 &lt;td>Create config template&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops report &amp;lt;path&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Generate HTML report&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops find &amp;lt;path&amp;gt; [filters...]&lt;/code>&lt;/td>
 &lt;td>Explore executions&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops archive &amp;lt;create|extract&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Archive and extract workdirs&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops cache &amp;lt;create|rebuild|list|show|stats&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Cache management&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>iops convert &amp;lt;file.xml&amp;gt;&lt;/code>&lt;/td>
 &lt;td>Convert JUBE XML to IOPS YAML&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="subcommand-reference">Subcommand Reference&lt;/h2>
&lt;h3 id="run---execute-benchmark">run - Execute Benchmark&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>iops run &amp;lt;config.yaml&amp;gt; &lt;span style="color:#f92672">[&lt;/span>options&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Options:&lt;/strong>&lt;/p></description></item><item><title>Custom Reports &amp; Visualization</title><link>https://iops.gitlabpages.inria.fr/user-guide/reporting/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/reporting/</guid><description>&lt;p>IOPS includes a reporting system that generates interactive HTML reports with custom plots and statistical analysis from your benchmark results.&lt;/p>
&lt;hr>
&lt;h2 id="table-of-contents">Table of Contents&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="#introduction">Introduction&lt;/a>&lt;/li>
&lt;li>&lt;a href="#basic-usage">Basic Usage&lt;/a>&lt;/li>
&lt;li>&lt;a href="#report-sections">Report Sections&lt;/a>&lt;/li>
&lt;li>&lt;a href="#controlling-sections">Controlling Sections&lt;/a>&lt;/li>
&lt;li>&lt;a href="#custom-plots">Custom Plots&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#logarithmic-axis-scales">Logarithmic Axis Scales&lt;/a>&lt;/li>
&lt;li>&lt;a href="#explicit-axis-ranges">Explicit Axis Ranges&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#default-plots">Default Plots&lt;/a>&lt;/li>
&lt;li>&lt;a href="#themes-and-styling">Themes and Styling&lt;/a>&lt;/li>
&lt;li>&lt;a href="#plot-sizing-and-defaults">Plot Sizing and Defaults&lt;/a>&lt;/li>
&lt;li>&lt;a href="#output-configuration">Output Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="#per-test-image-gallery">Per-Test Image Gallery&lt;/a>&lt;/li>
&lt;li>&lt;a href="#software-version-capture-probe">Software Version Capture Probe&lt;/a>&lt;/li>
&lt;li>&lt;a href="#examples">Examples&lt;/a>&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Reports are generated as self-contained HTML files with embedded interactive Plotly visualizations. You can auto-generate reports after execution, define custom plots and themes per metric, control which sections appear, regenerate reports with different configurations without re-running benchmarks, and optionally export all plots as image files (requires kaleido).&lt;/p></description></item><item><title>JUBE Conversion</title><link>https://iops.gitlabpages.inria.fr/user-guide/jube-conversion/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/user-guide/jube-conversion/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>Experimental Feature&lt;/strong>&lt;/p>
&lt;p>JUBE conversion is experimental. It may contain bugs or undergo breaking changes in future releases. Please report any issues you encounter.&lt;/p>
&lt;/blockquote>
&lt;p>Convert existing &lt;a href="https://github.com/FZJ-JSC/JUBE">JUBE&lt;/a> benchmark configurations to IOPS format using &lt;code>iops convert&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>JUBE (Juelich Benchmarking Environment) is an HPC benchmarking framework that uses XML configurations. &lt;code>iops convert&lt;/code> provides a best-effort translation of JUBE benchmarks to IOPS YAML, using JUBE&amp;rsquo;s own Python library to parse the XML for accurate interpretation of JUBE-specific syntax. The output includes TODO markers for features that require manual adjustment.&lt;/p></description></item><item><title>Measuring the runtime overhead of an I/O interception library</title><link>https://iops.gitlabpages.inria.fr/blog/toto-overhead-analysis/</link><pubDate>Sat, 30 May 2026 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/blog/toto-overhead-analysis/</guid><description>&lt;blockquote>
&lt;p>This study is part of our paper &lt;strong>&amp;ldquo;TOTO: Transparent I/O Tuning for HPC Applications&amp;rdquo;&lt;/strong>, accepted at &lt;strong>ICS 2026&lt;/strong> (International Conference on Supercomputing, Belfast, July 2026). The overhead measurements below were produced with IOPS.&lt;/p>
&lt;/blockquote>
&lt;p>When you intercept I/O system calls to analyze application behavior, how much overhead do you actually add? That&amp;rsquo;s the question we needed to answer for &lt;a href="https://gitlab.inria.fr/hpc_io/toto">TOTO&lt;/a> (Transparent and Online Tool for I/O), a library that hooks into I/O calls via &lt;code>LD_PRELOAD&lt;/code> to provide runtime analysis of parallel file system access patterns.&lt;/p></description></item><item><title>Using IOPS on Grid'5000</title><link>https://iops.gitlabpages.inria.fr/blog/grid500/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/blog/grid500/</guid><description>&lt;p>&lt;a href="https://www.grid5000.fr/w/Grid5000:Home">Grid'5000&lt;/a> is a large-scale and flexible testbed for experiment-driven research in all areas of computer science, with a focus on parallel and distributed computing, including Cloud, HPC, Big Data and AI.&lt;/p>
&lt;p>On Grid'5000 platform the reservation of computing nodes and scheduling of batch jobs is managed by the &lt;a href="https://oar.readthedocs.io/en/2.5/">OAR software suite&lt;/a>, while IOPS is only supporting &lt;a href="https://slurm.schedmd.com/documentation.html">Slurm&lt;/a> as a batch scheduler.
Thankfully, the high level of similarity between OAR and Slurm (at least from a user perspective) makes it easy to &lt;strong>run IOPS on Grid'5000 front-end with OAR being used to schedule &amp;amp; control the test jobs on the computing nodes&lt;/strong>. Let&amp;rsquo;s see how&amp;hellip;&lt;/p></description></item><item><title>Testing IOPS's BO search against random sampling using cached HPC results</title><link>https://iops.gitlabpages.inria.fr/blog/bayesian-optimization-study/</link><pubDate>Sat, 24 Jan 2026 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/blog/bayesian-optimization-study/</guid><description>&lt;p>IOPS can use &lt;a href="https://iops.gitlabpages.inria.fr/user-guide/search-methods/#bayesian-optimization">Bayesian Optimization&lt;/a> when the objective is to find the parameter combination that yields the maximum (or minimum) metric value in a search space. In this article, we decided to test the efficiency of IOPS&amp;rsquo;s BO search by comparing it against IOPS&amp;rsquo;s &lt;a href="https://iops.gitlabpages.inria.fr/user-guide/search-methods/#random-sampling">random search&lt;/a>. The idea is simple: given a search space, run both IOPS BO and Random, then compare the results to see which one reaches the optimal first. And of course, it should not be a surprise at this point that we used an I/O benchmark to do it.&lt;/p></description></item><item><title>Search</title><link>https://iops.gitlabpages.inria.fr/search/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://iops.gitlabpages.inria.fr/search/</guid><description>&lt;div id="search-container">
 &lt;input type="text" id="search-input" placeholder="Type to search..." autofocus>
 &lt;div id="search-results">&lt;/div>
&lt;/div>
&lt;style>
#search-container {
 max-width: 800px;
 margin: 0 auto;
}
#search-input {
 width: 100%;
 padding: 12px 16px;
 font-size: 18px;
 border: 2px solid #ddd;
 border-radius: 8px;
 margin-bottom: 20px;
 box-sizing: border-box;
}
#search-input:focus {
 outline: none;
 border-color: #0066cc;
}
#search-results {
 list-style: none;
 padding: 0;
}
.search-result {
 padding: 16px;
 margin-bottom: 12px;
 border: 1px solid #eee;
 border-radius: 8px;
 background: #fafafa;
}
.search-result:hover {
 background: #f0f0f0;
}
.search-result h3 {
 margin: 0 0 8px 0;
}
.search-result h3 a {
 color: #0066cc;
 text-decoration: none;
}
.search-result h3 a:hover {
 text-decoration: underline;
}
.search-result .section {
 display: inline-block;
 padding: 2px 8px;
 background: #e0e0e0;
 border-radius: 4px;
 font-size: 12px;
 margin-bottom: 8px;
 text-transform: capitalize;
}
.search-result .snippet {
 color: #666;
 font-size: 14px;
 line-height: 1.5;
}
.no-results {
 color: #666;
 font-style: italic;
}
&lt;/style>
&lt;script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0">&lt;/script>
&lt;script>
(function() {
 let fuse = null;
 let searchData = [];

 // Fetch the search index. The URL is injected by Hugo (head_custom.html) so
 // it matches the active baseURL, whether the site is served under /iops/ or
 // at a domain root. Fall back to a root-relative path if it is missing.
 const indexUrl = window.IOPS_SEARCH_INDEX || (window.location.origin + '/index.json');
 fetch(indexUrl)
 .then(response => response.json())
 .then(data => {
 searchData = data;
 fuse = new Fuse(data, {
 keys: [
 { name: 'title', weight: 3 },
 { name: 'section', weight: 2 },
 { name: 'content', weight: 1 }
 ],
 includeScore: true,
 threshold: 0.4,
 ignoreLocation: true
 });
 })
 .catch(err => {
 console.error('Failed to load search index:', err);
 document.getElementById('search-results').innerHTML =
 '&lt;p class="no-results">Failed to load search index. Please try again later.&lt;/p></description></item></channel></rss>