<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aeza</title>
    <description>The latest articles on DEV Community by Aeza (@aeza__net).</description>
    <link>https://dev.to/aeza__net</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4108163%2F58f1152c-71c2-4496-8db7-c426fb9c26e7.jpg</url>
      <title>DEV Community: Aeza</title>
      <link>https://dev.to/aeza__net</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aeza__net"/>
    <language>en</language>
    <item>
      <title>Ollama vs vLLM for Concurrent Inference: How to Choose the Right Server</title>
      <dc:creator>Aeza</dc:creator>
      <pubDate>Thu, 03 Sep 2026 14:13:06 +0000</pubDate>
      <link>https://dev.to/aeza__net/ollama-vs-vllm-for-concurrent-inference-how-to-choose-the-right-server-3o5p</link>
      <guid>https://dev.to/aeza__net/ollama-vs-vllm-for-concurrent-inference-how-to-choose-the-right-server-3o5p</guid>
      <description>&lt;p&gt;Choosing between Ollama and vLLM is not simply a matter of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which inference engine is faster?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer depends heavily on the workload.&lt;/p&gt;

&lt;p&gt;A personal chat interface with one active user has very different requirements from a multi-user API handling dozens of concurrent generations.&lt;/p&gt;

&lt;p&gt;For one workload, simple model management may matter more than maximum throughput.&lt;/p&gt;

&lt;p&gt;For another, the deciding factors may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;continuous batching&lt;/li&gt;
&lt;li&gt;queue behavior&lt;/li&gt;
&lt;li&gt;KV-cache utilization&lt;/li&gt;
&lt;li&gt;TTFT&lt;/li&gt;
&lt;li&gt;p99 latency&lt;/li&gt;
&lt;li&gt;GPU saturation&lt;/li&gt;
&lt;li&gt;overload handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means a useful Ollama vs vLLM comparison cannot be reduced to a single &lt;code&gt;tokens/s&lt;/code&gt; number.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a practical benchmark methodology for comparing the two under real concurrent load.&lt;/p&gt;

&lt;p&gt;By the end, you'll know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when vLLM's batching can provide a measurable advantage&lt;/li&gt;
&lt;li&gt;why Ollama may still be the better choice for smaller workloads&lt;/li&gt;
&lt;li&gt;how to build a fair concurrency sweep&lt;/li&gt;
&lt;li&gt;which latency and throughput metrics actually matter&lt;/li&gt;
&lt;li&gt;how to detect queueing and saturation&lt;/li&gt;
&lt;li&gt;how to choose the server that stays inside your SLO&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Short Answer
&lt;/h2&gt;

&lt;p&gt;There is no universal concurrency level at which vLLM suddenly becomes better than Ollama.&lt;/p&gt;

&lt;p&gt;vLLM begins to gain an advantage when enough requests are active or waiting for its scheduler to form useful batches while the GPU still has sufficient compute and memory capacity.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Low concurrency
      ↓
Little opportunity for batching
      ↓
Ollama may be completely sufficient

Higher concurrency
      ↓
More simultaneous requests
      ↓
Continuous batching becomes useful
      ↓
vLLM can improve aggregate throughput

Too much concurrency
      ↓
Queue grows
      ↓
TTFT and p99 increase
      ↓
OOM / timeout / rejected requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real question is therefore not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which engine reaches the highest tokens per second?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which engine delivers the required throughput while keeping latency, errors, memory usage, and operational complexity inside acceptable limits?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Start with the Workload, Not the Inference Engine
&lt;/h2&gt;

&lt;p&gt;Before testing either server, define what the service actually needs to handle.&lt;/p&gt;

&lt;p&gt;Three common scenarios illustrate why this matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Personal Chat
&lt;/h3&gt;

&lt;p&gt;A personal chat usually has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one active user&lt;/li&gt;
&lt;li&gt;low concurrency&lt;/li&gt;
&lt;li&gt;occasional model loading&lt;/li&gt;
&lt;li&gt;relatively tolerant startup latency&lt;/li&gt;
&lt;li&gt;no large server-side request queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this environment, maximum aggregate GPU throughput may not matter very much.&lt;/p&gt;

&lt;p&gt;Operational simplicity often matters more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small Internal API
&lt;/h3&gt;

&lt;p&gt;An internal API may receive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;several simultaneous users&lt;/li&gt;
&lt;li&gt;short request bursts&lt;/li&gt;
&lt;li&gt;varying context lengths&lt;/li&gt;
&lt;li&gt;occasional concurrency spikes&lt;/li&gt;
&lt;li&gt;moderate latency requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both Ollama and vLLM may fit this workload.&lt;/p&gt;

&lt;p&gt;The correct choice depends on the actual traffic curve.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Concurrency Inference Service
&lt;/h3&gt;

&lt;p&gt;A public or heavily used internal service may have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many simultaneous requests&lt;/li&gt;
&lt;li&gt;sustained queues&lt;/li&gt;
&lt;li&gt;strict TTFT targets&lt;/li&gt;
&lt;li&gt;p95/p99 latency requirements&lt;/li&gt;
&lt;li&gt;high GPU utilization targets&lt;/li&gt;
&lt;li&gt;overload conditions that must be handled predictably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where continuous batching and queue-aware scheduling become much more important.&lt;/p&gt;




&lt;h2&gt;
  
  
  Record the Workload Before Benchmarking
&lt;/h2&gt;

&lt;p&gt;At minimum, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input tokens&lt;/strong&gt; — determine prefill cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output tokens&lt;/strong&gt; — determine how long a request occupies generation resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average concurrency&lt;/strong&gt; — represents normal load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peak concurrency&lt;/strong&gt; — reveals saturation behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming ratio&lt;/strong&gt; — affects user-perceived latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context length&lt;/strong&gt; — directly affects KV-cache consumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request bursts&lt;/strong&gt; — determine queue behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target SLO&lt;/strong&gt; — defines when performance becomes unacceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Long contexts consume more KV-cache.&lt;/p&gt;

&lt;p&gt;Long generations keep execution resources occupied for longer.&lt;/p&gt;

&lt;p&gt;Average concurrency can also hide dangerous workload patterns.&lt;/p&gt;

&lt;p&gt;For example, imagine a service that usually has four active requests.&lt;/p&gt;

&lt;p&gt;That sounds moderate.&lt;/p&gt;

&lt;p&gt;But if several long conversations overlap at the same moment, the system may suddenly experience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KV-cache pressure
+
longer queue
+
higher TTFT
+
higher p99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even though average concurrency still looks reasonable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Higher server throughput does not automatically mean a better user experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A configuration can produce more total tokens per second while making individual requests noticeably slower.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Ollama Is Attractive for Smaller Workloads
&lt;/h2&gt;

&lt;p&gt;Ollama combines several parts of the local model lifecycle into one workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model downloading&lt;/li&gt;
&lt;li&gt;model storage&lt;/li&gt;
&lt;li&gt;Modelfiles&lt;/li&gt;
&lt;li&gt;configuration&lt;/li&gt;
&lt;li&gt;local HTTP API&lt;/li&gt;
&lt;li&gt;model loading&lt;/li&gt;
&lt;li&gt;model unloading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a developer or small team, this can make deployment and maintenance significantly easier.&lt;/p&gt;

&lt;p&gt;You can replace a model quickly, modify the system prompt, and control how long a model remains loaded without building a large serving stack.&lt;/p&gt;

&lt;p&gt;Cold-start performance should still be evaluated separately from warm inference.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important Ollama Parameters
&lt;/h2&gt;

&lt;p&gt;In a warmed-up environment, Ollama behavior depends on settings such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OLLAMA_NUM_PARALLEL
OLLAMA_MAX_QUEUE
OLLAMA_MAX_LOADED_MODELS
keep_alive
context size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parallel requests increase memory consumption because each active request may require additional context state.&lt;/p&gt;

&lt;p&gt;A configuration that works perfectly with short prompts may behave very differently with longer contexts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Short context
+
4 parallel requests
=
works normally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long context
+
4 parallel requests
=
larger memory usage
+
queue growth
+
possible failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So do not treat parallelism as an isolated setting.&lt;/p&gt;

&lt;p&gt;Context length matters too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ollama's Main Advantage
&lt;/h2&gt;

&lt;p&gt;For many workloads, Ollama's strongest advantage is not peak benchmark performance.&lt;/p&gt;

&lt;p&gt;It is operational simplicity.&lt;/p&gt;

&lt;p&gt;That can be extremely valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;personal assistants&lt;/li&gt;
&lt;li&gt;development environments&lt;/li&gt;
&lt;li&gt;prototypes&lt;/li&gt;
&lt;li&gt;small internal APIs&lt;/li&gt;
&lt;li&gt;teams that frequently change models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the workload does not create enough concurrency for continuous batching to matter, a more complicated serving stack may provide little practical benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why vLLM Benefits from Concurrency
&lt;/h2&gt;

&lt;p&gt;vLLM is designed around server-side inference workloads.&lt;/p&gt;

&lt;p&gt;Its scheduler can continuously adjust active batches as requests arrive and complete.&lt;/p&gt;

&lt;p&gt;Instead of waiting for one static batch to fully finish, the engine can use newly available capacity for other requests.&lt;/p&gt;

&lt;p&gt;This is commonly referred to as &lt;strong&gt;continuous batching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A ────────────────&amp;gt;
Request B      ────────────────&amp;gt;
Request C           ────────────────&amp;gt;

Scheduler continuously updates
the active batch as requests
arrive and finish.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;several requests are active&lt;/li&gt;
&lt;li&gt;more requests are waiting&lt;/li&gt;
&lt;li&gt;the GPU supports the required backend&lt;/li&gt;
&lt;li&gt;enough memory remains available&lt;/li&gt;
&lt;li&gt;batching improves accelerator utilization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With one short request, there may simply be nothing useful to batch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important vLLM Parameters
&lt;/h2&gt;

&lt;p&gt;Record the server configuration with every benchmark.&lt;/p&gt;

&lt;p&gt;Important settings include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--max-num-seqs&lt;/span&gt;
&lt;span class="nt"&gt;--max-model-len&lt;/span&gt;
&lt;span class="nt"&gt;--gpu-memory-utilization&lt;/span&gt;
&lt;span class="nt"&gt;--dtype&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also record whether chunked prefill is enabled and how it is configured.&lt;/p&gt;

&lt;p&gt;For multi-GPU deployments, record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--tensor-parallel-size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tensor parallelism can increase the effective memory available for larger models, but it also introduces communication between GPUs.&lt;/p&gt;

&lt;p&gt;That communication is not free.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;More GPUs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not automatically mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linear performance scaling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Monitor vLLM's Server-Side Behavior
&lt;/h2&gt;

&lt;p&gt;In addition to normal application metrics, monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request queue length&lt;/li&gt;
&lt;li&gt;KV-cache utilization&lt;/li&gt;
&lt;li&gt;GPU memory utilization&lt;/li&gt;
&lt;li&gt;preemption&lt;/li&gt;
&lt;li&gt;rejected requests&lt;/li&gt;
&lt;li&gt;OOM events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both Ollama and vLLM should also be monitored for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queue growth&lt;/li&gt;
&lt;li&gt;memory pressure&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;failed requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important difference is that vLLM exposes more of the machinery involved in high-concurrency serving.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Server Is Easier for One User?
&lt;/h2&gt;

&lt;p&gt;For a single user, Ollama will usually be easier to operate.&lt;/p&gt;

&lt;p&gt;The model lifecycle and API are integrated into one workflow.&lt;/p&gt;

&lt;p&gt;vLLM becomes more attractive when you specifically require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;high concurrent throughput&lt;/li&gt;
&lt;li&gt;continuous batching&lt;/li&gt;
&lt;li&gt;detailed production metrics&lt;/li&gt;
&lt;li&gt;specific GPU backends&lt;/li&gt;
&lt;li&gt;larger request queues&lt;/li&gt;
&lt;li&gt;a workload expected to grow rapidly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision should follow the workload rather than the popularity of either tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Make the Benchmark Fair
&lt;/h2&gt;

&lt;p&gt;A benchmark only makes sense if both systems are processing equivalent work.&lt;/p&gt;

&lt;p&gt;This sounds obvious.&lt;/p&gt;

&lt;p&gt;In practice, it is easy to get wrong.&lt;/p&gt;

&lt;p&gt;Try to keep the following equivalent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model weights&lt;/li&gt;
&lt;li&gt;model architecture&lt;/li&gt;
&lt;li&gt;precision&lt;/li&gt;
&lt;li&gt;quantization&lt;/li&gt;
&lt;li&gt;tokenizer&lt;/li&gt;
&lt;li&gt;chat template&lt;/li&gt;
&lt;li&gt;generation parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different weight formats may require different inference paths.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GGUF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;safetensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;do not automatically represent identical runtime behavior.&lt;/p&gt;

&lt;p&gt;If exactly the same model build cannot be used by both engines, document that clearly.&lt;/p&gt;

&lt;p&gt;The benchmark conclusion then applies only to the tested configurations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Record Model Sources and Hashes
&lt;/h2&gt;

&lt;p&gt;For reproducibility, save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model name
Model version
Weight source
File hash
Quantization
dtype
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, a future benchmark may use slightly different weights and produce results that appear inconsistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keep Generation Parameters Identical
&lt;/h2&gt;

&lt;p&gt;Use the same values for parameters such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max_tokens
temperature
top_p
stop
stream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deterministic benchmark workloads, you may use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temperature: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But matching generation settings is still not enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verify Tokenization and Chat Templates
&lt;/h2&gt;

&lt;p&gt;The same chat payload can produce different token sequences if the two servers use different tokenizers or templates.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Explain TCP slow start."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may be transformed differently before reaching the model.&lt;/p&gt;

&lt;p&gt;That means two systems may appear to be benchmarking the same request while actually processing different input lengths.&lt;/p&gt;

&lt;p&gt;Compare input-token counts before trusting the results.&lt;/p&gt;

&lt;p&gt;If one server sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;312 input tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and another sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;356 input tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the workload is not identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Record the Benchmark Environment
&lt;/h2&gt;

&lt;p&gt;Every run should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inference server version
Model name
Model hash
GPU model
VRAM
CPU
RAM
Operating system
Driver version
Context length
Generation settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A result such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8,200 output tokens/s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has little value without the environment that produced it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separate Cold and Warm Performance
&lt;/h2&gt;

&lt;p&gt;Cold-start performance and steady-state inference answer different questions.&lt;/p&gt;

&lt;p&gt;Measure them separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cold Test
&lt;/h3&gt;

&lt;p&gt;A cold test includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model loading&lt;/li&gt;
&lt;li&gt;initialization&lt;/li&gt;
&lt;li&gt;first request latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters for workloads where models are frequently unloaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm Test
&lt;/h3&gt;

&lt;p&gt;A warm test measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;steady-state throughput&lt;/li&gt;
&lt;li&gt;TTFT&lt;/li&gt;
&lt;li&gt;token generation&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;concurrency scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not combine cold and warm measurements into one average.&lt;/p&gt;

&lt;p&gt;They describe different behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handle Prompt Caching Consistently
&lt;/h2&gt;

&lt;p&gt;Prompt caching can significantly change benchmark results.&lt;/p&gt;

&lt;p&gt;Either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;disable prompt caching on both systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use it consistently on both systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not allow one server to reuse cached prompt state while the other processes every prompt from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Make Sure the Load Generator Is Not the Bottleneck
&lt;/h2&gt;

&lt;p&gt;Your client must have enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU&lt;/li&gt;
&lt;li&gt;network bandwidth&lt;/li&gt;
&lt;li&gt;connection capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to generate the intended load.&lt;/p&gt;

&lt;p&gt;Otherwise, you may accidentally benchmark the load generator rather than the inference server.&lt;/p&gt;

&lt;p&gt;Monitor the client machine during heavy tests too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build a Concurrency Sweep
&lt;/h2&gt;

&lt;p&gt;A simple benchmark matrix could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;input_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;

&lt;span class="na"&gt;output_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;

&lt;span class="na"&gt;repeats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact values should match your real workload.&lt;/p&gt;

&lt;p&gt;If production requests usually have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8k input tokens
+
1k output tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then a benchmark using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;256 input tokens
+
64 output tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may tell you very little about production behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Increase Concurrency Until Saturation
&lt;/h2&gt;

&lt;p&gt;Start at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;concurrency = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then increase gradually.&lt;/p&gt;

&lt;p&gt;For every level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;warm up the server&lt;/li&gt;
&lt;li&gt;execute several repeated runs&lt;/li&gt;
&lt;li&gt;collect latency&lt;/li&gt;
&lt;li&gt;collect throughput&lt;/li&gt;
&lt;li&gt;collect errors&lt;/li&gt;
&lt;li&gt;collect queue metrics&lt;/li&gt;
&lt;li&gt;record GPU utilization&lt;/li&gt;
&lt;li&gt;record GPU memory usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Continue until you reach the first unacceptable condition.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OOM
Timeout
HTTP 503
Rejected request
p99 &amp;gt; SLO
GPU memory exhaustion
Queue growth without recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That point is much more useful than the absolute maximum throughput.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closed-Loop vs Open-Loop Testing
&lt;/h2&gt;

&lt;p&gt;There are two common ways to generate concurrent load.&lt;/p&gt;

&lt;p&gt;They measure different things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closed-Loop Load
&lt;/h3&gt;

&lt;p&gt;A closed-loop generator maintains a fixed number of active requests.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concurrency = 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever one request finishes, another starts.&lt;/p&gt;

&lt;p&gt;This answers questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does the server behave with 16 continuously active clients?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Open-Loop Load
&lt;/h3&gt;

&lt;p&gt;An open-loop generator sends requests at a defined arrival rate.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 requests/second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests continue arriving regardless of how quickly previous requests finish.&lt;/p&gt;

&lt;p&gt;This is useful for exposing queueing and overload behavior.&lt;/p&gt;

&lt;p&gt;If the server can process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;15 requests/second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while receiving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 requests/second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 requests/second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are being added to the queue.&lt;/p&gt;

&lt;p&gt;The queue may continue growing until:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency becomes unacceptable&lt;/li&gt;
&lt;li&gt;requests time out&lt;/li&gt;
&lt;li&gt;requests are rejected&lt;/li&gt;
&lt;li&gt;memory is exhausted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not combine closed-loop and open-loop measurements into a single curve.&lt;/p&gt;

&lt;p&gt;They answer different questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Throughput Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Aggregate tokens per second describes server capacity.&lt;/p&gt;

&lt;p&gt;It does not fully describe the user experience.&lt;/p&gt;

&lt;p&gt;For every concurrency level, collect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requests per second&lt;/li&gt;
&lt;li&gt;output tokens per second&lt;/li&gt;
&lt;li&gt;TTFT&lt;/li&gt;
&lt;li&gt;ITL&lt;/li&gt;
&lt;li&gt;TPOT&lt;/li&gt;
&lt;li&gt;end-to-end latency&lt;/li&gt;
&lt;li&gt;successful-request rate&lt;/li&gt;
&lt;li&gt;queue length&lt;/li&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need both throughput and latency.&lt;/p&gt;




&lt;h2&gt;
  
  
  TTFT: Time to First Token
&lt;/h2&gt;

&lt;p&gt;TTFT measures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request sent
      ↓
First actual generated token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not automatically treat the first streaming chunk as the first token.&lt;/p&gt;

&lt;p&gt;Some APIs may send metadata or other fields before actual generated text appears.&lt;/p&gt;

&lt;p&gt;Measure the first real generated output.&lt;/p&gt;

&lt;p&gt;TTFT strongly affects perceived responsiveness in chat applications.&lt;/p&gt;

&lt;p&gt;A user may tolerate a long generation if text begins appearing quickly.&lt;/p&gt;

&lt;p&gt;A long silent wait before the first token often feels much worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  ITL: Inter-Token Latency
&lt;/h2&gt;

&lt;p&gt;ITL describes the delay between generated tokens.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token 1
   ↓ 40 ms
Token 2
   ↓ 43 ms
Token 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This describes how smoothly the response streams after generation begins.&lt;/p&gt;

&lt;p&gt;If the client measures network chunks rather than actual tokens, call the measurement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inter-chunk latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  TPOT: Time per Output Token
&lt;/h2&gt;

&lt;p&gt;TPOT can be represented conceptually as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generation time after first token
---------------------------------
remaining output tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It helps separate initial responsiveness from steady-state generation speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measure End-to-End Latency
&lt;/h2&gt;

&lt;p&gt;End-to-end latency measures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request sent
      ↓
Queueing
      ↓
Prefill
      ↓
Generation
      ↓
Final response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is often the metric most directly connected to the total user wait time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Always Measure Tail Latency
&lt;/h2&gt;

&lt;p&gt;Do not report only the average.&lt;/p&gt;

&lt;p&gt;At minimum, report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p50
p95
p99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because batching and queueing may affect a minority of requests much more severely than the median request.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p50 = 1.8 s
p95 = 4.2 s
p99 = 12.7 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An average of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.4 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would hide an important part of the user experience.&lt;/p&gt;

&lt;p&gt;For production inference APIs, p99 often matters much more than the mean.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understand the Throughput-Latency Trade-Off
&lt;/h2&gt;

&lt;p&gt;Continuous batching can improve aggregate throughput.&lt;/p&gt;

&lt;p&gt;But that does not mean every request becomes faster.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concurrency 4
Throughput: 3,000 tok/s
p99 TTFT: 400 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concurrency 32
Throughput: 7,500 tok/s
p99 TTFT: 4.8 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second configuration produces much more aggregate throughput.&lt;/p&gt;

&lt;p&gt;But it may violate the application's SLO.&lt;/p&gt;

&lt;p&gt;If your requirement is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p99 TTFT &amp;lt; 2 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the higher-throughput operating point is not acceptable.&lt;/p&gt;

&lt;p&gt;The best benchmark result is therefore not necessarily the highest point on the throughput curve.&lt;/p&gt;

&lt;p&gt;It is the highest useful operating point &lt;strong&gt;inside the SLO&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measure Queueing Explicitly
&lt;/h2&gt;

&lt;p&gt;Queueing is one of the most important parts of concurrent inference.&lt;/p&gt;

&lt;p&gt;Imagine requests arriving faster than they can be processed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arrival rate
     ↓
[Request]
[Request]
[Request]
[Request]
     ↓
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the server cannot keep up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue length ↑
TTFT ↑
p99 ↑
timeouts ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why a large or unlimited queue does not solve overload.&lt;/p&gt;

&lt;p&gt;It only changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rejected request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;very slow request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backpressure and overload behavior should therefore be part of the benchmark.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Fairness Between Short and Long Requests
&lt;/h2&gt;

&lt;p&gt;A realistic workload rarely contains identical prompts.&lt;/p&gt;

&lt;p&gt;Create a mixed workload with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;short inputs&lt;/li&gt;
&lt;li&gt;long inputs&lt;/li&gt;
&lt;li&gt;short outputs&lt;/li&gt;
&lt;li&gt;long outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then check whether short requests are excessively delayed by long ones.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A:
256 input tokens
64 output tokens

Request B:
8,000 input tokens
2,000 output tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A scheduler should not allow a large request to make every small request unreasonably slow.&lt;/p&gt;

&lt;p&gt;Also observe whether large prefill operations create visible pauses for requests already generating tokens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Store Raw Benchmark Data
&lt;/h2&gt;

&lt;p&gt;A professional benchmark should produce reproducible artifacts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;benchmark/
├── requests.jsonl
├── run.json
├── results.csv
├── server.log
└── gpu.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal result schema could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server,run,concurrency,input_tokens,output_tokens,ttft_ms,itl_ms,e2e_ms,status,error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timestamp,gpu_utilization,gpu_memory_used,queue_length
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshots alone are not sufficient.&lt;/p&gt;

&lt;p&gt;Raw data allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recalculate percentiles&lt;/li&gt;
&lt;li&gt;change aggregation windows&lt;/li&gt;
&lt;li&gt;inspect outliers&lt;/li&gt;
&lt;li&gt;compare future server versions&lt;/li&gt;
&lt;li&gt;reproduce the analysis&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Graph Throughput and Latency Together
&lt;/h2&gt;

&lt;p&gt;A useful benchmark graph uses concurrency on the X-axis.&lt;/p&gt;

&lt;p&gt;Then plot metrics such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requests/s
Output tokens/s
TTFT p50
TTFT p95
TTFT p99
End-to-end p99
Queue length
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important shape often looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Throughput
    /
   /
  /
 /_____
       \
        saturation

Latency
        /
       /
______/ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At low concurrency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;throughput increases&lt;/li&gt;
&lt;li&gt;latency remains stable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Near saturation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;throughput growth slows&lt;/li&gt;
&lt;li&gt;queueing increases&lt;/li&gt;
&lt;li&gt;p99 rises rapidly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That knee in the curve is often more important than the absolute maximum.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compare API Compatibility Before Migrating
&lt;/h2&gt;

&lt;p&gt;Both Ollama and vLLM may expose OpenAI-compatible interfaces.&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI-compatible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;behaviorally identical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the features your application actually uses.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chat Completions&lt;/li&gt;
&lt;li&gt;streaming&lt;/li&gt;
&lt;li&gt;structured output&lt;/li&gt;
&lt;li&gt;embeddings&lt;/li&gt;
&lt;li&gt;tool calling&lt;/li&gt;
&lt;li&gt;generation parameters&lt;/li&gt;
&lt;li&gt;stop sequences&lt;/li&gt;
&lt;li&gt;error responses&lt;/li&gt;
&lt;li&gt;token limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not discover incompatibilities after production traffic has already been switched.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Readiness Is More Than the Inference Engine
&lt;/h2&gt;

&lt;p&gt;A working HTTP endpoint is not automatically a production-ready inference service.&lt;/p&gt;

&lt;p&gt;The surrounding platform may need to provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLS
Authentication
Rate limiting
Logging
Monitoring
Health checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A health check should also distinguish between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server process is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model is loaded and ready to serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A listening TCP port does not guarantee that the next request will meet the latency SLO.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compare Observability
&lt;/h2&gt;

&lt;p&gt;For concurrent production workloads, observability becomes part of the product decision.&lt;/p&gt;

&lt;p&gt;Useful vLLM signals may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queue state&lt;/li&gt;
&lt;li&gt;KV-cache utilization&lt;/li&gt;
&lt;li&gt;TTFT&lt;/li&gt;
&lt;li&gt;request latency&lt;/li&gt;
&lt;li&gt;memory pressure&lt;/li&gt;
&lt;li&gt;preemption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ollama also exposes timing information around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model loading&lt;/li&gt;
&lt;li&gt;prompt evaluation&lt;/li&gt;
&lt;li&gt;generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardless of the server, collect the same client-side metrics.&lt;/p&gt;

&lt;p&gt;Client-side instrumentation provides the most direct view of what users actually experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Upgrades Before Production
&lt;/h2&gt;

&lt;p&gt;Inference-server updates can change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory consumption&lt;/li&gt;
&lt;li&gt;scheduler behavior&lt;/li&gt;
&lt;li&gt;inference backends&lt;/li&gt;
&lt;li&gt;configuration defaults&lt;/li&gt;
&lt;li&gt;chat templates&lt;/li&gt;
&lt;li&gt;model compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before upgrading production, replay a realistic workload against the new version.&lt;/p&gt;

&lt;p&gt;Keep the previous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Container image or package
Configuration
Model hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;available for rollback.&lt;/p&gt;

&lt;p&gt;A benchmark that is valid for one version should not automatically be assumed valid for the next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use a Shared API Layer to Keep Migration Easy
&lt;/h2&gt;

&lt;p&gt;If possible, avoid coupling application logic directly to one inference engine.&lt;/p&gt;

&lt;p&gt;Keep:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model names&lt;/li&gt;
&lt;li&gt;generation parameters&lt;/li&gt;
&lt;li&gt;timeout logic&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;behind an internal adapter.&lt;/p&gt;

&lt;p&gt;Then make the server endpoint configurable.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     ↓
Internal inference adapter
     ↓
Ollama OR vLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it much easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;benchmark alternatives&lt;/li&gt;
&lt;li&gt;canary a new server&lt;/li&gt;
&lt;li&gt;roll back&lt;/li&gt;
&lt;li&gt;migrate later&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Canary the Migration
&lt;/h2&gt;

&lt;p&gt;Do not send 100% of production traffic to the new server immediately.&lt;/p&gt;

&lt;p&gt;Start with a small percentage.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5% new server
95% old server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;errors&lt;/li&gt;
&lt;li&gt;throughput&lt;/li&gt;
&lt;li&gt;queue growth&lt;/li&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then increase gradually if the results remain inside the SLO.&lt;/p&gt;

&lt;p&gt;Keep the old server available for rapid rollback.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Benchmark Workflow
&lt;/h2&gt;

&lt;p&gt;Here is a reusable process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define the Real Workload
&lt;/h3&gt;

&lt;p&gt;Record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input length
Output length
Average concurrency
Peak concurrency
Request rate
Streaming ratio
SLO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Normalize the Models
&lt;/h3&gt;

&lt;p&gt;Match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weights
Precision
Quantization
Tokenizer
Chat template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Match Generation Settings
&lt;/h3&gt;

&lt;p&gt;Use identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max_tokens
temperature
top_p
stop
stream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Record the Environment
&lt;/h3&gt;

&lt;p&gt;Save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server version
Model hash
GPU
VRAM
CPU
RAM
OS
Driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Warm Up the Server
&lt;/h3&gt;

&lt;p&gt;Separate cold-start measurements from steady-state results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Run the Concurrency Sweep
&lt;/h3&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
4
8
16
32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7: Measure Latency and Throughput
&lt;/h3&gt;

&lt;p&gt;Collect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requests/s
Tokens/s
TTFT
ITL
TPOT
E2E latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8: Measure Tail Latency
&lt;/h3&gt;

&lt;p&gt;Calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p50
p95
p99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9: Watch Queue and GPU State
&lt;/h3&gt;

&lt;p&gt;Collect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue length
GPU utilization
GPU memory
KV-cache
OOM events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10: Stop at the SLO Boundary
&lt;/h3&gt;

&lt;p&gt;The benchmark should stop being considered "better" once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p99 &amp;gt; SLO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even if tokens per second continue increasing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting Recommendation by Scenario
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single User
&lt;/h3&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model is supported&lt;/li&gt;
&lt;li&gt;cold start is acceptable&lt;/li&gt;
&lt;li&gt;memory consumption is acceptable&lt;/li&gt;
&lt;li&gt;latency is acceptable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simplicity may be worth more than extra concurrent throughput.&lt;/p&gt;




&lt;h3&gt;
  
  
  Small Internal API
&lt;/h3&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama or vLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benchmark:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request bursts&lt;/li&gt;
&lt;li&gt;moderate concurrency&lt;/li&gt;
&lt;li&gt;queue behavior&lt;/li&gt;
&lt;li&gt;p95/p99&lt;/li&gt;
&lt;li&gt;overload handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either system may be the better fit.&lt;/p&gt;




&lt;h3&gt;
  
  
  High-Concurrency GPU Service
&lt;/h3&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;throughput improves with concurrency&lt;/li&gt;
&lt;li&gt;p99 remains inside the SLO&lt;/li&gt;
&lt;li&gt;TTFT remains acceptable&lt;/li&gt;
&lt;li&gt;KV-cache remains healthy&lt;/li&gt;
&lt;li&gt;queue growth remains controlled&lt;/li&gt;
&lt;li&gt;OOM does not occur&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not assume the answer before measuring it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Before declaring one server faster than the other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Use comparable model weights&lt;/li&gt;
&lt;li&gt;[ ] Match precision and quantization&lt;/li&gt;
&lt;li&gt;[ ] Match tokenizer behavior&lt;/li&gt;
&lt;li&gt;[ ] Match chat templates&lt;/li&gt;
&lt;li&gt;[ ] Match generation parameters&lt;/li&gt;
&lt;li&gt;[ ] Record software versions&lt;/li&gt;
&lt;li&gt;[ ] Record GPU and VRAM&lt;/li&gt;
&lt;li&gt;[ ] Separate cold and warm tests&lt;/li&gt;
&lt;li&gt;[ ] Use realistic input lengths&lt;/li&gt;
&lt;li&gt;[ ] Use realistic output lengths&lt;/li&gt;
&lt;li&gt;[ ] Sweep concurrency&lt;/li&gt;
&lt;li&gt;[ ] Test open-loop and closed-loop load separately&lt;/li&gt;
&lt;li&gt;[ ] Measure requests per second&lt;/li&gt;
&lt;li&gt;[ ] Measure output tokens per second&lt;/li&gt;
&lt;li&gt;[ ] Measure TTFT&lt;/li&gt;
&lt;li&gt;[ ] Measure ITL or TPOT&lt;/li&gt;
&lt;li&gt;[ ] Measure end-to-end latency&lt;/li&gt;
&lt;li&gt;[ ] Calculate p50/p95/p99&lt;/li&gt;
&lt;li&gt;[ ] Monitor queue length&lt;/li&gt;
&lt;li&gt;[ ] Monitor GPU utilization&lt;/li&gt;
&lt;li&gt;[ ] Monitor GPU memory&lt;/li&gt;
&lt;li&gt;[ ] Record OOM and rejected requests&lt;/li&gt;
&lt;li&gt;[ ] Preserve raw benchmark data&lt;/li&gt;
&lt;li&gt;[ ] Evaluate results against the SLO&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Ollama and vLLM solve overlapping problems, but they optimize for different priorities.&lt;/p&gt;

&lt;p&gt;Ollama is attractive when you value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast setup&lt;/li&gt;
&lt;li&gt;simple model management&lt;/li&gt;
&lt;li&gt;a straightforward local lifecycle&lt;/li&gt;
&lt;li&gt;moderate concurrency&lt;/li&gt;
&lt;li&gt;lower operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;vLLM becomes more attractive when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;continuous batching&lt;/li&gt;
&lt;li&gt;higher concurrent throughput&lt;/li&gt;
&lt;li&gt;efficient KV-cache management&lt;/li&gt;
&lt;li&gt;high GPU utilization&lt;/li&gt;
&lt;li&gt;larger request queues&lt;/li&gt;
&lt;li&gt;detailed production observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct comparison is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which server produces the most tokens per second?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which server delivers the throughput you need while keeping TTFT, p99 latency, queueing, errors, memory usage, and operational complexity inside acceptable limits?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If vLLM produces higher aggregate throughput but pushes p99 beyond your SLO, that is not a win.&lt;/p&gt;

&lt;p&gt;If Ollama already handles your real traffic comfortably, migrating to a more complex serving stack may provide little practical benefit.&lt;/p&gt;

&lt;p&gt;Benchmark the workload you actually have.&lt;/p&gt;

&lt;p&gt;Find the saturation point.&lt;/p&gt;

&lt;p&gt;Measure the queue.&lt;/p&gt;

&lt;p&gt;Measure the tails.&lt;/p&gt;

&lt;p&gt;Then choose the simplest system that satisfies the SLO.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>CPU Steal Time Explained: What %st Really Means on a Virtual Machine</title>
      <dc:creator>Aeza</dc:creator>
      <pubDate>Thu, 03 Sep 2026 14:11:28 +0000</pubDate>
      <link>https://dev.to/aeza__net/cpu-steal-time-explained-what-st-really-means-on-a-virtual-machine-109h</link>
      <guid>https://dev.to/aeza__net/cpu-steal-time-explained-what-st-really-means-on-a-virtual-machine-109h</guid>
      <description>&lt;p&gt;CPU steal time is one of those Linux metrics that looks simple until you actually need to diagnose a performance problem.&lt;/p&gt;

&lt;p&gt;You open &lt;code&gt;top&lt;/code&gt;, notice that &lt;code&gt;%st&lt;/code&gt; is above zero, and the obvious conclusion seems to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The host is overloaded.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or perhaps:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another VM on the server is stealing my CPU.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both explanations are possible.&lt;/p&gt;

&lt;p&gt;Neither can be proven from &lt;code&gt;%st&lt;/code&gt; alone.&lt;/p&gt;

&lt;p&gt;A useful diagnosis requires at least three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the steal-time measurement itself&lt;/li&gt;
&lt;li&gt;CPU pressure inside the VM&lt;/li&gt;
&lt;li&gt;measurable impact on the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, that means correlating &lt;code&gt;%st&lt;/code&gt; with metrics such as the run queue, p95/p99 latency, throughput, per-vCPU utilization, and cgroup throttling.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a practical workflow for doing exactly that.&lt;/p&gt;

&lt;p&gt;By the end, you'll know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what &lt;code&gt;%st&lt;/code&gt; actually measures&lt;/li&gt;
&lt;li&gt;how to collect steal-time data correctly&lt;/li&gt;
&lt;li&gt;when high steal time is worth investigating&lt;/li&gt;
&lt;li&gt;how to separate host contention from guest CPU saturation&lt;/li&gt;
&lt;li&gt;what evidence to collect before contacting your hosting provider&lt;/li&gt;
&lt;li&gt;how to monitor steal time continuously&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Short Answer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;%st&lt;/code&gt; represents time during which a virtual CPU was ready to run but did not receive physical CPU time from the host.&lt;/p&gt;

&lt;p&gt;But a high value only becomes operationally meaningful when it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;abnormal for that specific VM&lt;/li&gt;
&lt;li&gt;sustained across multiple measurement intervals&lt;/li&gt;
&lt;li&gt;correlated with CPU pressure or application degradation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A single spike is not enough.&lt;/p&gt;

&lt;p&gt;A much stronger signal looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Elevated %st
+
Growing CPU run queue
+
Increasing p99 latency
+
Falling throughput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That combination is worth investigating.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does &lt;code&gt;%st&lt;/code&gt; Mean in &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;vmstat&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;In a virtual machine, &lt;code&gt;%st&lt;/code&gt; — or steal time — represents the percentage of a measurement interval during which a vCPU was ready to execute but did not receive physical CPU time.&lt;/p&gt;

&lt;p&gt;You may see it in &lt;code&gt;top&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%Cpu(s): 12.7 us, 6.4 sy, 0.0 ni, 72.2 id,
          3.1 wa, 0.0 hi, 0.0 si, 5.6 st
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5.6 st
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means that approximately 5.6% of the measured CPU interval was accounted as steal time.&lt;/p&gt;

&lt;p&gt;The important part is understanding &lt;em&gt;why&lt;/em&gt; this can happen.&lt;/p&gt;

&lt;p&gt;A VM does not control the physical CPU directly.&lt;/p&gt;

&lt;p&gt;There is another scheduling layer underneath it.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Virtual Machine Has Two CPU Schedulers
&lt;/h2&gt;

&lt;p&gt;To understand steal time, separate the guest scheduler from the host scheduler.&lt;/p&gt;

&lt;h3&gt;
  
  
  The guest scheduler
&lt;/h3&gt;

&lt;p&gt;The operating system inside the VM decides:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which process should run on this vCPU?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nginx
postgres
python
node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may all compete for CPU time inside the guest.&lt;/p&gt;

&lt;h3&gt;
  
  
  The host scheduler
&lt;/h3&gt;

&lt;p&gt;The physical host or hypervisor decides something different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When does this vCPU get access to an actual physical CPU?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gives us two scheduling layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application process
        ↓
Guest OS scheduler
        ↓
       vCPU
        ↓
Host / hypervisor scheduler
        ↓
Physical CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the guest has a runnable process.&lt;/p&gt;

&lt;p&gt;The guest scheduler wants to execute it.&lt;/p&gt;

&lt;p&gt;The vCPU is therefore ready.&lt;/p&gt;

&lt;p&gt;But the host scheduler does not immediately schedule that vCPU onto a physical core.&lt;/p&gt;

&lt;p&gt;The guest waits.&lt;/p&gt;

&lt;p&gt;That waiting time can be recorded as steal time.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Guest has runnable work
        ↓
vCPU is ready
        ↓
Physical CPU is not assigned
        ↓
Guest waits
        ↓
Time is recorded as steal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why &lt;code&gt;%st&lt;/code&gt; applies to the vCPU as a whole rather than to one particular process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Not Confuse &lt;code&gt;%st&lt;/code&gt; with Other CPU Metrics
&lt;/h2&gt;

&lt;p&gt;Several CPU metrics can indicate performance pressure, but they describe different things.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;%usr&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Time spent executing user-space code.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application logic&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;database execution&lt;/li&gt;
&lt;li&gt;compression&lt;/li&gt;
&lt;li&gt;model inference&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;%sys&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Time spent executing kernel code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;%idle&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;CPU time during which the guest had no runnable work.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;%iowait&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Time associated with waiting while I/O is pending.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;%st&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Time during which a runnable virtual CPU did not receive physical CPU time.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%usr / %sys → guest is actively using CPU

%iowait     → work is waiting around I/O

%st         → guest wanted CPU but the host did not provide it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;%iowait&lt;/code&gt; is therefore &lt;strong&gt;not&lt;/strong&gt; another form of steal time.&lt;/p&gt;




&lt;h2&gt;
  
  
  CPU Throttling Is Also Not the Same as Steal Time
&lt;/h2&gt;

&lt;p&gt;A process can be CPU-limited inside the VM even when &lt;code&gt;%st&lt;/code&gt; is zero.&lt;/p&gt;

&lt;p&gt;One common cause is a cgroup CPU quota.&lt;/p&gt;

&lt;p&gt;With cgroup v2, inspect the configured quota:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/cpu.max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inspect CPU statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/cpu.stat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful counters can include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nr_throttled
throttled_usec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If these counters increase while the workload is running, the process or cgroup may be hitting a CPU quota.&lt;/p&gt;

&lt;p&gt;That means the CPU restriction originates &lt;strong&gt;inside the guest environment&lt;/strong&gt;, not necessarily from hypervisor scheduling.&lt;/p&gt;

&lt;p&gt;So this situation is possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while application latency still increases because the process is being throttled.&lt;/p&gt;

&lt;p&gt;This distinction matters when trying to determine whether the VM itself is overloaded or whether the underlying platform is contributing to the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Zero &lt;code&gt;%st&lt;/code&gt; Does Not Prove the Host Is Healthy
&lt;/h2&gt;

&lt;p&gt;Another common mistake is assuming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No host-side scheduling problems exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That conclusion is too strong.&lt;/p&gt;

&lt;p&gt;A zero value only tells you that the guest kernel did not account for steal time during that particular interval.&lt;/p&gt;

&lt;p&gt;The availability of steal-time accounting depends on factors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hypervisor&lt;/li&gt;
&lt;li&gt;guest kernel&lt;/li&gt;
&lt;li&gt;architecture&lt;/li&gt;
&lt;li&gt;platform configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;KVM can expose steal time on supported systems, but the guest still needs access to the relevant accounting information.&lt;/p&gt;

&lt;p&gt;Other forms of host-side delay may also exist without appearing as &lt;code&gt;%st&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So think of zero as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No steal time was recorded here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The physical host is definitely healthy.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Measure Steal Time over Intervals
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to misread steal time is to use the wrong measurement window.&lt;/p&gt;

&lt;p&gt;For example, the first row printed by &lt;code&gt;vmstat&lt;/code&gt; can represent averages since system boot unless you suppress it.&lt;/p&gt;

&lt;p&gt;A since-boot average is nearly useless when investigating a short incident.&lt;/p&gt;

&lt;p&gt;Imagine that your VM has been running for 30 days.&lt;/p&gt;

&lt;p&gt;A two-minute scheduling problem may almost disappear inside the long-term average.&lt;/p&gt;

&lt;p&gt;For incident analysis, collect interval-based measurements instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Collect One Minute of &lt;code&gt;vmstat&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A useful starting point is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C vmstat &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 1 60 | &lt;span class="nb"&gt;tee &lt;/span&gt;vmstat.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 sample per second
×
60 samples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;-y&lt;/code&gt; skips the misleading initial since-boot row.&lt;/p&gt;

&lt;p&gt;The output allows you to observe steal time together with metrics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;us&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wa&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;st&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;during exactly the same intervals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Collect Per-vCPU Data with &lt;code&gt;mpstat&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A system-wide CPU average can hide an important detail.&lt;/p&gt;

&lt;p&gt;Suppose a four-vCPU machine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU 0 → heavily delayed
CPU 1 → mostly idle
CPU 2 → mostly idle
CPU 3 → mostly idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The average across all CPUs may look relatively normal.&lt;/p&gt;

&lt;p&gt;To inspect individual virtual CPUs, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 60 | &lt;span class="nb"&gt;tee &lt;/span&gt;mpstat.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you compare each vCPU separately.&lt;/p&gt;

&lt;p&gt;That becomes especially important for workloads that rely heavily on a small number of threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does the &lt;code&gt;r&lt;/code&gt; Column in &lt;code&gt;vmstat&lt;/code&gt; Mean?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;r&lt;/code&gt; field represents runnable tasks.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r =
tasks currently executing
+
tasks ready and waiting for CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not an exact queue size for each individual CPU core.&lt;/p&gt;

&lt;p&gt;But it is useful as a CPU-pressure signal.&lt;/p&gt;

&lt;p&gt;Consider these two situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Situation A
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st: brief spike
r: normal
p99: normal
throughput: normal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There may be nothing operationally important happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Situation B
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st: elevated
r: increasing
p99: increasing
throughput: falling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the evidence is much stronger.&lt;/p&gt;

&lt;p&gt;The second case tells us that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the VM is waiting for CPU&lt;/li&gt;
&lt;li&gt;runnable work is accumulating&lt;/li&gt;
&lt;li&gt;users are experiencing measurable degradation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the kind of correlation worth investigating.&lt;/p&gt;




&lt;h2&gt;
  
  
  Record the Environment with Every Incident
&lt;/h2&gt;

&lt;p&gt;Raw performance numbers are much less useful without environmental context.&lt;/p&gt;

&lt;p&gt;At minimum, collect the timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;--iso-8601&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kernel version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Number of processors visible to the guest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Virtualization type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemd-detect-virt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And hypervisor information where available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C lscpu | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'/Hypervisor vendor/p;/Virtualization type/p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UTC timestamp
Kernel version
Number of vCPUs
Virtualization type
VM plan
CPU limits
Region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because two results such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st = 8%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may describe completely different environments.&lt;/p&gt;

&lt;p&gt;An 8% reading on a one-vCPU burstable instance is not automatically comparable to 8% on an eight-vCPU VM with a different CPU policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Establish a Baseline Before Calling &lt;code&gt;%st&lt;/code&gt; High
&lt;/h2&gt;

&lt;p&gt;There is no universal steal-time threshold.&lt;/p&gt;

&lt;p&gt;You will sometimes see rules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st &amp;gt; 5% = bad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st &amp;gt; 10% = overloaded host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These rules are convenient.&lt;/p&gt;

&lt;p&gt;They are also too simplistic.&lt;/p&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this value abnormal for this VM under a comparable workload?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build a baseline using measurements collected during:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low-load periods&lt;/li&gt;
&lt;li&gt;normal-load periods&lt;/li&gt;
&lt;li&gt;comparable hours&lt;/li&gt;
&lt;li&gt;comparable weekdays&lt;/li&gt;
&lt;li&gt;similar request volumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then calculate values such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;median
p95
p99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for the steal-time series.&lt;/p&gt;

&lt;p&gt;The goal is to understand the normal distribution for the machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Maximum Is Often a Bad Metric
&lt;/h2&gt;

&lt;p&gt;Suppose you observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum %st this month: 35%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds alarming.&lt;/p&gt;

&lt;p&gt;But what if the 35% value lasted for one second and nothing happened to the application?&lt;/p&gt;

&lt;p&gt;Now compare that with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st: 7–10%
Duration: 12 minutes
p99 latency: +70%
Throughput: -20%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second event may be much more important even though its maximum value is lower.&lt;/p&gt;

&lt;p&gt;For performance incidents, persistence and application impact often matter more than the highest isolated value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rebuild the Baseline After Major VM Changes
&lt;/h2&gt;

&lt;p&gt;The old baseline may stop being meaningful after changing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;number of vCPUs&lt;/li&gt;
&lt;li&gt;kernel version&lt;/li&gt;
&lt;li&gt;VM class&lt;/li&gt;
&lt;li&gt;service plan&lt;/li&gt;
&lt;li&gt;CPU limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, moving from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 vCPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 vCPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;changes thread distribution and the way aggregate CPU statistics should be interpreted.&lt;/p&gt;

&lt;p&gt;Treat significant configuration changes as the beginning of a new baseline period.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Should You Investigate High Steal Time?
&lt;/h2&gt;

&lt;p&gt;A useful rule is to investigate when all three conditions begin to appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;%st&lt;/code&gt; is above normal
&lt;/h3&gt;

&lt;p&gt;Not simply above an arbitrary internet threshold.&lt;/p&gt;

&lt;p&gt;It should be above the historical level for that VM.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The condition persists
&lt;/h3&gt;

&lt;p&gt;Several consecutive elevated intervals are more important than a single isolated sample.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Something measurable becomes worse
&lt;/h3&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU run queue increases&lt;/li&gt;
&lt;li&gt;p95 latency increases&lt;/li&gt;
&lt;li&gt;p99 latency increases&lt;/li&gt;
&lt;li&gt;throughput decreases&lt;/li&gt;
&lt;li&gt;errors increase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A weak signal might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st: 12%
Duration: 1 second
p99: unchanged
Throughput: unchanged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stronger signal might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st: elevated for 8 minutes
r: increased
p99: +80%
Throughput: -25%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second case is much more actionable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separate Host Contention from Guest CPU Saturation
&lt;/h2&gt;

&lt;p&gt;Steal time does not prevent the guest from also being CPU-bound.&lt;/p&gt;

&lt;p&gt;Both can happen at the same time.&lt;/p&gt;

&lt;p&gt;Start by examining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%usr
%sys
%idle
r
%st
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose you observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High %usr
High %sys
Low %idle
r &amp;gt; number of vCPUs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guest itself may already be CPU saturated.&lt;/p&gt;

&lt;p&gt;That does not mean &lt;code&gt;%st&lt;/code&gt; is irrelevant.&lt;/p&gt;

&lt;p&gt;It means you may have multiple sources of delay.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inspect Individual vCPUs
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can expose per-vCPU imbalance that disappears from the average.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU 0 → 100% busy
CPU 1 → 20%
CPU 2 → 15%
CPU 3 → 10%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single CPU-bound thread may saturate one virtual CPU while the rest remain mostly free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inspect Application Threads
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;pidstat&lt;/code&gt; to inspect thread-level CPU usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C pidstat &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; PID 1 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is one thread consuming nearly all available CPU?&lt;/li&gt;
&lt;li&gt;Is CPU usage distributed evenly?&lt;/li&gt;
&lt;li&gt;Is the application itself creating the bottleneck?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after checking these guest-side causes does it become safer to focus on the host.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Not Ignore I/O, Locks, or External Dependencies
&lt;/h2&gt;

&lt;p&gt;High latency does not automatically mean CPU contention.&lt;/p&gt;

&lt;p&gt;Other possibilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storage latency&lt;/li&gt;
&lt;li&gt;database locking&lt;/li&gt;
&lt;li&gt;application locks&lt;/li&gt;
&lt;li&gt;garbage collection&lt;/li&gt;
&lt;li&gt;network latency&lt;/li&gt;
&lt;li&gt;external APIs&lt;/li&gt;
&lt;li&gt;DNS&lt;/li&gt;
&lt;li&gt;remote databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High latency ≠ automatically CPU contention
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High %st ≠ automatically a noisy neighbor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful diagnosis should eliminate alternative explanations before assigning a cause.&lt;/p&gt;




&lt;h2&gt;
  
  
  Correlate &lt;code&gt;%st&lt;/code&gt; with Application Metrics
&lt;/h2&gt;

&lt;p&gt;The most useful analysis begins when system metrics and application metrics share the same timeline.&lt;/p&gt;

&lt;p&gt;Use UTC for everything.&lt;/p&gt;

&lt;p&gt;For each interval, compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st
r
request rate
throughput
error rate
p50 latency
p95 latency
p99 latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to answer a much better question than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Was steal time high?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did application performance become worse during exactly the same period?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Do Not Compare Different Aggregation Windows
&lt;/h2&gt;

&lt;p&gt;Imagine this comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st → one-second samples

p99 → five-minute window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CPU scheduling event lasting 30 seconds may disappear almost completely inside a five-minute application aggregate.&lt;/p&gt;

&lt;p&gt;The metrics then appear unrelated even when they describe the same incident.&lt;/p&gt;

&lt;p&gt;Try to align:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timestamps&lt;/li&gt;
&lt;li&gt;collection intervals&lt;/li&gt;
&lt;li&gt;aggregation windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;as closely as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  A "Noisy Neighbor" Is a Hypothesis, Not a Measurement
&lt;/h2&gt;

&lt;p&gt;One of the most common explanations for steal time is another VM consuming physical CPU resources on the same host.&lt;/p&gt;

&lt;p&gt;That is the classic "noisy neighbor" scenario.&lt;/p&gt;

&lt;p&gt;It may be correct.&lt;/p&gt;

&lt;p&gt;But guest metrics alone cannot tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which VM caused the delay&lt;/li&gt;
&lt;li&gt;whether another tenant caused it&lt;/li&gt;
&lt;li&gt;whether the host itself was busy&lt;/li&gt;
&lt;li&gt;whether a scheduler policy was involved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A noisy neighbor caused our outage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A technically defensible statement would be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;During this interval, the VM experienced increased CPU steal time while the run queue and application latency also increased.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That statement describes what you actually measured.&lt;/p&gt;

&lt;p&gt;Host telemetry is needed for stronger attribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Does the Host-Contention Hypothesis Become Stronger?
&lt;/h2&gt;

&lt;p&gt;The hypothesis becomes more convincing when degradation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeats under similar traffic&lt;/li&gt;
&lt;li&gt;coincides with elevated &lt;code&gt;%st&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;is not explained by a deployment&lt;/li&gt;
&lt;li&gt;is not explained by garbage collection&lt;/li&gt;
&lt;li&gt;is not explained by I/O&lt;/li&gt;
&lt;li&gt;is not explained by cgroup throttling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Host-side scheduling metrics provide the strongest confirmation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Store Raw Data, Not Just Screenshots
&lt;/h2&gt;

&lt;p&gt;A useful incident dataset might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;incident/
├── metadata.txt
├── vmstat.txt
├── mpstat.txt
├── application.csv
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal application/system CSV could use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timestamp_utc,st,r,usr,sys,p95_ms,p99_ms,rps,error_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Raw data lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recalculate percentiles&lt;/li&gt;
&lt;li&gt;change aggregation windows&lt;/li&gt;
&lt;li&gt;inspect individual events&lt;/li&gt;
&lt;li&gt;compare multiple incidents&lt;/li&gt;
&lt;li&gt;reproduce the analysis later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A screenshot cannot do that.&lt;/p&gt;

&lt;p&gt;Once metrics have been compressed into an image, much of the original information is gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test the Hypothesis with Controlled CPU Load
&lt;/h2&gt;

&lt;p&gt;A controlled test can help you understand how the VM behaves under a known CPU workload.&lt;/p&gt;

&lt;p&gt;First record the &lt;code&gt;stress-ng&lt;/code&gt; version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stress-ng &lt;span class="nt"&gt;--version&lt;/span&gt; | &lt;span class="nb"&gt;tee &lt;/span&gt;stress-ng-version.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run a CPU-bound workload pinned to one vCPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;taskset &lt;span class="nt"&gt;-c&lt;/span&gt; 0 stress-ng &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu-method&lt;/span&gt; matrixprod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt; 120s &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metrics-brief&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;tee &lt;/span&gt;stress-ng.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, collect system metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C vmstat &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 1 120 | &lt;span class="nb"&gt;tee &lt;/span&gt;vmstat.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 120 | &lt;span class="nb"&gt;tee &lt;/span&gt;mpstat.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Production warning:&lt;/strong&gt; Do not deliberately saturate CPU on a production VM unless you have an approved test or maintenance window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A controlled test is useful because one known variable is introduced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Predictable CPU workload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then observe how the VM and its metrics respond.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compare Like with Like
&lt;/h2&gt;

&lt;p&gt;Do not compare &lt;code&gt;stress-ng&lt;/code&gt; numbers from unrelated systems as if they were equivalent.&lt;/p&gt;

&lt;p&gt;Try to keep the following constant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stress-ng&lt;/code&gt; version&lt;/li&gt;
&lt;li&gt;CPU method&lt;/li&gt;
&lt;li&gt;operating-system image&lt;/li&gt;
&lt;li&gt;CPU class&lt;/li&gt;
&lt;li&gt;vCPU count&lt;/li&gt;
&lt;li&gt;CPU limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, differences may come from the test environment rather than from the platform behavior you are trying to measure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Can the Hosting Provider See That You Cannot?
&lt;/h2&gt;

&lt;p&gt;Inside the VM, you only see guest-level evidence.&lt;/p&gt;

&lt;p&gt;The infrastructure team may have access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vCPU thread wait time&lt;/li&gt;
&lt;li&gt;host oversubscription&lt;/li&gt;
&lt;li&gt;scheduler statistics&lt;/li&gt;
&lt;li&gt;physical CPU utilization&lt;/li&gt;
&lt;li&gt;vCPU pinning&lt;/li&gt;
&lt;li&gt;host events&lt;/li&gt;
&lt;li&gt;hypervisor-level contention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those measurements are much closer to the actual scheduling layer.&lt;/p&gt;

&lt;p&gt;If possible, ask the provider to compare its host-side data with the exact same UTC interval from your guest logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Diagnostic Workflow
&lt;/h2&gt;

&lt;p&gt;Here is a simple sequence you can reuse during an incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Measure &lt;code&gt;%st&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Collect interval-based &lt;code&gt;vmstat&lt;/code&gt; data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C vmstat &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 1 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Check the run queue
&lt;/h3&gt;

&lt;p&gt;Look at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask whether runnable work is accumulating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Inspect each vCPU
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for imbalance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Inspect application threads
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTC &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C pidstat &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; PID 1 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check whether the guest workload itself is saturating CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Check cgroup throttling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/cpu.stat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for increasing throttling counters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Align application metrics
&lt;/h3&gt;

&lt;p&gt;Compare the same interval for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p95
p99
throughput
error rate
request rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7: Eliminate alternative causes
&lt;/h3&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I/O&lt;/li&gt;
&lt;li&gt;garbage collection&lt;/li&gt;
&lt;li&gt;locks&lt;/li&gt;
&lt;li&gt;deployments&lt;/li&gt;
&lt;li&gt;network dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 8: Ask for host telemetry
&lt;/h3&gt;

&lt;p&gt;If the symptoms still point toward host CPU scheduling, ask the platform team to inspect the corresponding host interval.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Write a Useful Support Ticket
&lt;/h2&gt;

&lt;p&gt;Do not start with an accusation.&lt;/p&gt;

&lt;p&gt;This is weak:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your server is overloaded because another customer is stealing our CPU.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You cannot prove that from guest metrics.&lt;/p&gt;

&lt;p&gt;Instead, write something measurable.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Between 10:14 and 10:22 UTC, &lt;code&gt;%st&lt;/code&gt; increased significantly relative to the normal baseline for this VM. During the same interval, the CPU run queue increased and application p99 latency exceeded the SLO.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gives the provider a concrete period and measurable symptoms to investigate.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM identifier&lt;/li&gt;
&lt;li&gt;region&lt;/li&gt;
&lt;li&gt;number of vCPUs&lt;/li&gt;
&lt;li&gt;kernel version&lt;/li&gt;
&lt;li&gt;virtualization type&lt;/li&gt;
&lt;li&gt;exact UTC interval&lt;/li&gt;
&lt;li&gt;normal baseline&lt;/li&gt;
&lt;li&gt;observed &lt;code&gt;%st&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;run queue&lt;/li&gt;
&lt;li&gt;p95/p99&lt;/li&gt;
&lt;li&gt;throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attach your raw data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metadata.txt
vmstat.txt
mpstat.txt
application.csv
README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before sending anything, remove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;passwords&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;access tokens&lt;/li&gt;
&lt;li&gt;user data&lt;/li&gt;
&lt;li&gt;sensitive request contents&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Repeat the Test After Migration or a Plan Change
&lt;/h2&gt;

&lt;p&gt;Suppose the provider migrates the VM to another host.&lt;/p&gt;

&lt;p&gt;Or you change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the VM plan&lt;/li&gt;
&lt;li&gt;CPU limits&lt;/li&gt;
&lt;li&gt;vCPU count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not simply look at the next random workload and declare the problem fixed.&lt;/p&gt;

&lt;p&gt;Repeat a comparable test.&lt;/p&gt;

&lt;p&gt;Keep the following as consistent as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Duration
Input workload
Number of processes
Traffic profile
Measurement interval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One run is weak evidence.&lt;/p&gt;

&lt;p&gt;Several repeated runs are much stronger.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
%st elevated
p99 elevated
throughput reduced

After migration:
%st lower
p99 restored
throughput restored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that pattern repeats across several comparable tests, the case for a platform-related effect becomes much stronger.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitor Steal Time with Prometheus
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;node_exporter&lt;/code&gt;, CPU steal time can be observed through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_cpu_seconds_total{mode="steal"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A five-minute percentage can be calculated with PromQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 * avg by (instance) (
  rate(node_cpu_seconds_total{mode="steal"}[5m])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts the cumulative steal-time counter into an approximate percentage over the selected window.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Not Alert on a Single Spike
&lt;/h2&gt;

&lt;p&gt;A simplistic alert might look conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st &amp;gt; X
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this ignores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the VM's normal baseline&lt;/li&gt;
&lt;li&gt;duration&lt;/li&gt;
&lt;li&gt;user impact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more useful alerting model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st above expected baseline
AND
condition persists
AND
application impact exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application impact might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p99 &amp;gt; SLO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run queue increased
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;throughput decreased
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error rate increased
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Prometheus, the &lt;code&gt;for&lt;/code&gt; clause can prevent a single temporary spike from creating an incident.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the condition must remain true for ten minutes before the alert fires.&lt;/p&gt;

&lt;p&gt;The exact duration should depend on the workload and SLO.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitor the Monitoring System Too
&lt;/h2&gt;

&lt;p&gt;One subtle problem remains.&lt;/p&gt;

&lt;p&gt;Suppose your dashboard shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That only means something if the metric is actually being collected.&lt;/p&gt;

&lt;p&gt;If the exporter stops reporting the time series entirely, you should not interpret the missing metric as zero.&lt;/p&gt;

&lt;p&gt;A reliable monitoring system should therefore distinguish between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Steal time is zero
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Steal-time telemetry disappeared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitoring the monitoring pipeline is part of production observability.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three-Signal Model
&lt;/h2&gt;

&lt;p&gt;A useful way to think about steal-time incidents is to use three layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Scheduling signal
        ↓
       %st

2. CPU pressure
        ↓
        r

3. User-visible impact
        ↓
 p95 / p99 / throughput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strongest diagnosis appears when all three move together.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st ↑
r ↑
p99 ↑
throughput ↓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is strong evidence that CPU scheduling pressure is having a measurable effect.&lt;/p&gt;

&lt;p&gt;By contrast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%st spike
r unchanged
p99 unchanged
throughput unchanged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may not justify any operational response at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Checklist
&lt;/h2&gt;

&lt;p&gt;When you notice elevated CPU steal time, work through this list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Collect &lt;code&gt;%st&lt;/code&gt; using interval-based measurements&lt;/li&gt;
&lt;li&gt;[ ] Record UTC timestamps&lt;/li&gt;
&lt;li&gt;[ ] Check the &lt;code&gt;vmstat&lt;/code&gt; run queue&lt;/li&gt;
&lt;li&gt;[ ] Inspect individual vCPUs with &lt;code&gt;mpstat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Inspect application threads with &lt;code&gt;pidstat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Check cgroup CPU throttling&lt;/li&gt;
&lt;li&gt;[ ] Compare p95 and p99 latency&lt;/li&gt;
&lt;li&gt;[ ] Compare throughput&lt;/li&gt;
&lt;li&gt;[ ] Compare error rate&lt;/li&gt;
&lt;li&gt;[ ] Match aggregation windows&lt;/li&gt;
&lt;li&gt;[ ] Compare against the VM's baseline&lt;/li&gt;
&lt;li&gt;[ ] Check I/O and application bottlenecks&lt;/li&gt;
&lt;li&gt;[ ] Preserve raw data&lt;/li&gt;
&lt;li&gt;[ ] Request host telemetry if needed&lt;/li&gt;
&lt;li&gt;[ ] Repeat the test after migration or configuration changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;CPU steal time &lt;code&gt;%st&lt;/code&gt; tells you that a runnable vCPU did not receive physical CPU time during part of a measurement interval.&lt;/p&gt;

&lt;p&gt;That information is useful.&lt;/p&gt;

&lt;p&gt;But it is not a complete diagnosis.&lt;/p&gt;

&lt;p&gt;Do not ask only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is &lt;code&gt;%st&lt;/code&gt; high?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is &lt;code&gt;%st&lt;/code&gt; persistently abnormal for this VM, and does it coincide with CPU pressure and measurable application degradation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A solid investigation combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interval-based steal-time measurements&lt;/li&gt;
&lt;li&gt;CPU run queue&lt;/li&gt;
&lt;li&gt;per-vCPU utilization&lt;/li&gt;
&lt;li&gt;cgroup throttling&lt;/li&gt;
&lt;li&gt;application latency&lt;/li&gt;
&lt;li&gt;throughput&lt;/li&gt;
&lt;li&gt;historical baseline&lt;/li&gt;
&lt;li&gt;host telemetry when available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without the run queue and application impact, &lt;code&gt;%st&lt;/code&gt; remains an observation.&lt;/p&gt;

&lt;p&gt;With synchronized system and application metrics, it becomes evidence you can actually use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>performance</category>
      <category>virtualization</category>
    </item>
  </channel>
</rss>
