<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Using k6 for load testing on Grafana Labs</title><link>https://grafana.com/docs/loki/v3.7.x/send-data/k6/</link><description>Recent content in Using k6 for load testing on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/loki/v3.7.x/send-data/k6/index.xml" rel="self" type="application/rss+xml"/><item><title>Generating log data for testing</title><link>https://grafana.com/docs/loki/v3.7.x/send-data/k6/log-generation/</link><pubDate>Thu, 09 Apr 2026 02:28:18 +0000</pubDate><guid>https://grafana.com/docs/loki/v3.7.x/send-data/k6/log-generation/</guid><content><![CDATA[&lt;h1 id=&#34;generating-log-data-for-testing&#34;&gt;Generating log data for testing&lt;/h1&gt;
&lt;p&gt;You can use k6 to generate log data for load testing.&lt;/p&gt;
&lt;h2 id=&#34;using-pushparameterized&#34;&gt;Using &lt;code&gt;pushParameterized&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Push logs to Loki with &lt;code&gt;pushParameterized&lt;/code&gt;.
This method generates batches of streams in a random fashion.
This method requires three arguments:&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;name&lt;/th&gt;
              &lt;th&gt;description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;streams&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;number of streams per batch&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;minSize&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;minimum batch size in bytes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;maxSize&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;maximum batch size in bytes&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import loki from &amp;#39;k6/x/loki&amp;#39;;

const KB = 1024;
const MB = KB * KB;

const conf = loki.Config(&amp;#34;http://localhost:3100&amp;#34;);
const client = loki.Client(conf);

export default () =&amp;gt; {
   client.pushParameterized(2, 500 * KB, 1 * MB);
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;argument-streams&#34;&gt;Argument &lt;code&gt;streams&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The first argument of the method is the desired amount of streams per batch.
Instead of using a fixed amount of streams in each call, you can randomize the
value to simulate a more realistic scenario.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min &amp;#43; 1) &amp;#43; min);
};

export default () =&amp;gt; {
   let streams = randomInt(2, 8);
   client.pushParameterized(streams, 500 * KB, 1 * MB);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;arguments-minsize-and-maxsize&#34;&gt;Arguments &lt;code&gt;minSize&lt;/code&gt; and &lt;code&gt;maxSize&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The second and third argument of the method take the lower and upper bound of
the batch size. The resulting batch size is a random value between the two
arguments. This mimics the behavior of a log client, such as Grafana Alloy,
where logs are buffered and pushed once a certain batch size
is reached or after a certain size when no logs have been received.&lt;/p&gt;
&lt;p&gt;The batch size is not equal to the payload size, as the batch size only counts
bytes of the raw logs. The payload may be compressed when Protobuf encoding
is used.&lt;/p&gt;
&lt;h2 id=&#34;log-format&#34;&gt;Log format&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;xk6-loki&lt;/code&gt; can emit log lines in seven distinct formats. The label &lt;code&gt;format&lt;/code&gt; of
a stream defines the format of its log lines.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache common (&lt;code&gt;apache_common&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Apache combined (&lt;code&gt;apache_combined&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Apache error (&lt;code&gt;apache_error&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc3164&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;BSD syslog&lt;/a&gt; (&lt;code&gt;rfc3164&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc5424&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Syslog&lt;/a&gt; (&lt;code&gt;rfc5424&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;JSON (&lt;code&gt;json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/kr/logfmt&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;logfmt&lt;/a&gt; (&lt;code&gt;logfmt&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Under the hood, the extension uses a fork the library
&lt;a href=&#34;https://github.com/mingrammer/flog&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;flog&lt;/a&gt; for generating log lines.&lt;/p&gt;
&lt;h2 id=&#34;labels&#34;&gt;Labels&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;xk6-loki&lt;/code&gt; uses the following label names for generating streams:&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;name&lt;/th&gt;
              &lt;th&gt;type&lt;/th&gt;
              &lt;th&gt;cardinality&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;instance&lt;/td&gt;
              &lt;td&gt;fixed&lt;/td&gt;
              &lt;td&gt;1 per k6 worker&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;format&lt;/td&gt;
              &lt;td&gt;fixed&lt;/td&gt;
              &lt;td&gt;7&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;os&lt;/td&gt;
              &lt;td&gt;fixed&lt;/td&gt;
              &lt;td&gt;3&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;namespace&lt;/td&gt;
              &lt;td&gt;variable&lt;/td&gt;
              &lt;td&gt;&amp;gt;100&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;app&lt;/td&gt;
              &lt;td&gt;variable&lt;/td&gt;
              &lt;td&gt;&amp;gt;100&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;pod&lt;/td&gt;
              &lt;td&gt;variable&lt;/td&gt;
              &lt;td&gt;&amp;gt;100&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;language&lt;/td&gt;
              &lt;td&gt;variable&lt;/td&gt;
              &lt;td&gt;&amp;gt;100&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;word&lt;/td&gt;
              &lt;td&gt;variable&lt;/td&gt;
              &lt;td&gt;&amp;gt;100&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;By default, variable labels are not used.
However, you can specify the
cardinality (quantity of distinct label values) using the &lt;code&gt;cardinality&lt;/code&gt; argument
in the &lt;code&gt;Config&lt;/code&gt; constructor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import loki from &amp;#39;k6/x/loki&amp;#39;;

const cardinality = {
   &amp;#34;app&amp;#34;: 1,
   &amp;#34;namespace&amp;#34;: 2,
   &amp;#34;language&amp;#34;: 2,
   &amp;#34;pod&amp;#34;: 5,
};
const conf = loki.Config(&amp;#34;http://localhost:3100&amp;#34;, 5000, 1.0, cardinality);
const client = loki.Client(conf);&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The total quantity of distinct streams is defined by the cartesian product of
all label values. Keep in mind that high cardinality negatively impacts the performance of
the Loki instance.&lt;/p&gt;
&lt;h2 id=&#34;payload-encoding&#34;&gt;Payload encoding&lt;/h2&gt;
&lt;p&gt;Loki accepts two kinds of push payload encodings: JSON and Protobuf.
While JSON is easier for humans to read,
Protobuf is optimized for performance
and should be preferred when possible.&lt;/p&gt;
&lt;p&gt;To define the ratio of Protobuf to JSON requests, the client
configuration accepts values of 0.0 to 1.0.
0.0 means 100% JSON encoding, and 1.0 means 100% Protobuf encoding.&lt;/p&gt;
&lt;p&gt;The default value is 0.9.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import loki from &amp;#39;k6/x/loki&amp;#39;;

const ratio = 0.8; // 80% Protobuf, 20% JSON
const conf = loki.Config(&amp;#34;http://localhost:3100&amp;#34;, 5000, ratio);
const client = loki.Client(conf);&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
]]></content><description>&lt;h1 id="generating-log-data-for-testing">Generating log data for testing&lt;/h1>
&lt;p>You can use k6 to generate log data for load testing.&lt;/p>
&lt;h2 id="using-pushparameterized">Using &lt;code>pushParameterized&lt;/code>&lt;/h2>
&lt;p>Push logs to Loki with &lt;code>pushParameterized&lt;/code>.
This method generates batches of streams in a random fashion.
This method requires three arguments:&lt;/p></description></item><item><title>Use k6 to load test the write path</title><link>https://grafana.com/docs/loki/v3.7.x/send-data/k6/write-scenario/</link><pubDate>Thu, 09 Apr 2026 02:28:18 +0000</pubDate><guid>https://grafana.com/docs/loki/v3.7.x/send-data/k6/write-scenario/</guid><content><![CDATA[&lt;h1 id=&#34;use-k6-to-load-test-the-write-path&#34;&gt;Use k6 to load test the write path&lt;/h1&gt;
&lt;p&gt;There are multiple considerations when
load testing a Loki cluster&amp;rsquo;s write path.&lt;/p&gt;
&lt;p&gt;The most important consideration is the setup of the target cluster.
Keep these items in mind when setting up your load test for the target cluster.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Deployment mode. The cluster might be deployed as
a single-binary, as a simple scalable deployment, or as microservices&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quantity of component instances. This aids in predicting the need
to horizontally scale the quantity of component instances.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resource allocation such as CPU, memory, disk, and network.
This aids in predicting the need to vertically scale the
underlying hardware.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These parameters can be adjusted in the load test:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The quantity of distinct labels and their cardinality&lt;/p&gt;
&lt;p&gt;This will define how many active streams your load test will generate.
Start with a small number of label values,
to keep the quantity of streams small enough,
such that it does not overwhelm your cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The batch size the client sends&lt;/p&gt;
&lt;p&gt;The batch size indirectly controls how many log lines per push request are
sent. The smaller the batch size, and the larger the quantity
of active streams you have,
the longer it takes before chunks are flushed.
Keeping lots of chunks
in the ingester increases memory consumption.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The number of virtual users (VUs)&lt;/p&gt;
&lt;p&gt;VUs can be used to control the amount of parallelism with which logs should
be pushed. Every VU runs its own loop of iterations.
Therefore, the number of VUs has the most impact on
the generated log throughput.
Since generating logs is CPU-intensive, there is a threshold above which
increasing the number VUs does not result in a higher amount of log data.
A rule of thumb is that the
most data can be generated when the number of VUs are set to 1-1.5 times
the quantity of CPU cores available on the k6 worker machine.
For example,
set the value in the range of 8 to 12 for an 8-core machine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The way to run k6&lt;/p&gt;
&lt;p&gt;k6 supports three &lt;a href=&#34;/docs/k6/latest/get-started/running-k6/#execution-modes&#34;&gt;execution modes&lt;/a&gt; to run a test: local, distributed, and cloud.
Whereas running your k6 load test from a single (local
or remote) machine is easy to set up and fine for smaller Loki clusters,
the single machine does not load test large Loki installations,
because it cannot create the data to saturate the write path.
For larger tests, consider &lt;a href=&#34;/docs/k6/latest/testing-guides/running-large-tests/&#34;&gt;these optimizations&lt;/a&gt;, or run them in &lt;a href=&#34;/products/cloud/k6&#34;&gt;Grafana Cloud k6&lt;/a&gt; or a Kubernetes cluster with the &lt;a href=&#34;https://github.com/grafana/k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 Operator&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;metrics&#34;&gt;Metrics&lt;/h2&gt;
&lt;p&gt;The extension collects two metrics that are printed in the
&lt;a href=&#34;/docs/k6/latest/results-output/end-of-test/&#34;&gt;end-of-test summary&lt;/a&gt; in addition to the built-in metrics.&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;name&lt;/th&gt;
              &lt;th&gt;description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_client_uncompressed_bytes&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;the quantity of uncompressed log data pushed to Loki, in bytes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_client_lines&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;the number of log lines pushed to Loki&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;k6-value-checks&#34;&gt;k6 value checks&lt;/h2&gt;
&lt;p&gt;An HTTP request that successfully pushes logs to Loki
responds with status &lt;code&gt;204 No Content&lt;/code&gt;.
The status code should be checked explicitly with a &lt;a href=&#34;/docs/k6/latest/javascript-api/k6/check/&#34;&gt;k6 check&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;javascript-example&#34;&gt;Javascript example&lt;/h2&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check, fail } from &amp;#39;k6&amp;#39;;
import loki from &amp;#39;k6/x/loki&amp;#39;;

/*
 * Host name with port
 * @constant {string}
 */
const HOST = &amp;#34;localhost:3100&amp;#34;;

/**
 * Name of the Loki tenant
 * passed as X-Scope-OrgID header to requests.
 * If tenant is omitted, xk6-loki runs in multi-tenant mode,
 * and every VU will use its own ID.
 * @constant {string}
 */
const TENANT_ID = &amp;#34;my_org_id&amp;#34;

/**
 * URL used for push and query requests
 * Path is automatically appended by the client
 * @constant {string}
 */
const BASE_URL = `${TENANT_ID}@${HOST}`;

/**
 * Minimum amount of virtual users (VUs)
 * @constant {number}
 */
const MIN_VUS = 1

/**
 * Maximum amount of virtual users (VUs)
 * @constant {number}
 */
const MAX_VUS = 10;

/**
 * Constants for byte values
 * @constant {number}
 */
const KB = 1024;
const MB = KB * KB;

/**
 * Definition of test scenario
 */
export const options = {
  thresholds: {
    &amp;#39;http_req_failed&amp;#39;: [{ threshold: &amp;#39;rate&amp;lt;=0.01&amp;#39;, abortOnFail: true }],
  },
  scenarios: {
    write: {
      executor: &amp;#39;ramping-vus&amp;#39;,
      exec: &amp;#39;write&amp;#39;,
      startVUs: MIN_VUS,
      stages: [
        { duration: &amp;#39;5m&amp;#39;, target: MAX_VUS },
        { duration: &amp;#39;30m&amp;#39;, target: MAX_VUS },
      ],
      gracefulRampDown: &amp;#39;1m&amp;#39;,
    },
  },
};

const labelCardinality = {
  &amp;#34;app&amp;#34;: 5,
  &amp;#34;namespace&amp;#34;: 1,
};
const timeout = 10000; // 10s
const ratio = 0.9; // 90% Protobuf
const conf = new loki.Config(BASE_URL, timeout, ratio, labelCardinality);
const client = new loki.Client(conf);

/**
 * Entrypoint for write scenario
 */
export function write() {
  let streams = randomInt(4, 8);
  let res = client.pushParameterized(streams, 800 * KB, 1 * MB);
  check(res,
    {
      &amp;#39;successful write&amp;#39;: (res) =&amp;gt; {
        let success = res.status === 204;
        if (!success) console.log(res.status, res.body);
        return success;
      },
    }
  );
}

/**
 * Return a random integer between min and max including min and max
 */
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min &amp;#43; 1) &amp;#43; min);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
]]></content><description>&lt;h1 id="use-k6-to-load-test-the-write-path">Use k6 to load test the write path&lt;/h1>
&lt;p>There are multiple considerations when
load testing a Loki cluster&amp;rsquo;s write path.&lt;/p>
&lt;p>The most important consideration is the setup of the target cluster.
Keep these items in mind when setting up your load test for the target cluster.&lt;/p></description></item><item><title>Use k6 to load test log queries</title><link>https://grafana.com/docs/loki/v3.7.x/send-data/k6/query-scenario/</link><pubDate>Thu, 09 Apr 2026 02:28:18 +0000</pubDate><guid>https://grafana.com/docs/loki/v3.7.x/send-data/k6/query-scenario/</guid><content><![CDATA[&lt;h1 id=&#34;use-k6-to-load-test-log-queries&#34;&gt;Use k6 to load test log queries&lt;/h1&gt;
&lt;p&gt;When designing a test scenario for load testing the read path of a Loki
installation, it is important to know what types of queries you expect.&lt;/p&gt;
&lt;h2 id=&#34;supported-query-types&#34;&gt;Supported query types&lt;/h2&gt;
&lt;p&gt;Loki has 5 types of queries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;instant query&lt;/li&gt;
&lt;li&gt;range query&lt;/li&gt;
&lt;li&gt;labels query&lt;/li&gt;
&lt;li&gt;label values query&lt;/li&gt;
&lt;li&gt;series query&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a real-world use-case, such as querying Loki using it as a Grafana
data source, all of these queries are used. Each of them has a different

    &lt;a href=&#34;/docs/loki/v3.7.x/reference/loki-http-api/&#34;&gt;API&lt;/a&gt; endpoint. The xk6-loki extension
provides a &lt;a href=&#34;https://github.com/grafana/xk6-loki#javascript-api&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Javascript API&lt;/a&gt;
for all these query types.&lt;/p&gt;
&lt;h3 id=&#34;instant-query&#34;&gt;Instant query&lt;/h3&gt;
&lt;p&gt;Instant queries can be executed using the function &lt;code&gt;instantQuery(query, limit)&lt;/code&gt;
on the &lt;code&gt;loki.Client&lt;/code&gt; instance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export default () =&amp;gt; {
  client.instantQuery(`rate({app=&amp;#34;my-app-name&amp;#34;} | logfmt | level=&amp;#34;error&amp;#34; [5m])`);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;range-query&#34;&gt;Range query&lt;/h3&gt;
&lt;p&gt;Range queries can be executed using the function &lt;code&gt;rangeQuery(query, duration, limit)&lt;/code&gt;
on the &lt;code&gt;loki.Client&lt;/code&gt; instance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export default () =&amp;gt; {
  client.rangeQuery(`{app=&amp;#34;my-app-name&amp;#34;} | logfmt | level=&amp;#34;error&amp;#34;`, &amp;#34;15m&amp;#34;);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;labels-query&#34;&gt;Labels query&lt;/h3&gt;
&lt;p&gt;Labels queries can be executed using the function &lt;code&gt;labelsQuery(duration)&lt;/code&gt;
on the &lt;code&gt;loki.Client&lt;/code&gt; instance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export default () =&amp;gt; {
  client.labelsQuery(&amp;#34;10m&amp;#34;);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;label-values-query&#34;&gt;Label values query&lt;/h3&gt;
&lt;p&gt;Label values queries can be executed using the function &lt;code&gt;labelValuesQuery(label, duration)&lt;/code&gt;
on the &lt;code&gt;loki.Client&lt;/code&gt; instance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export default () =&amp;gt; {
  client.labelValuesQuery(&amp;#34;app&amp;#34;, &amp;#34;10m&amp;#34;);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;series-query&#34;&gt;Series query&lt;/h3&gt;
&lt;p&gt;Series queries can be executed using the function &lt;code&gt;seriesQuery(matcher, range)&lt;/code&gt;
on the &lt;code&gt;loki.Client&lt;/code&gt; instance:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export default () =&amp;gt; {
  client.seriesQuery(`match[]={app=~&amp;#34;loki-.*&amp;#34;}`, &amp;#34;10m&amp;#34;);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;metrics&#34;&gt;Metrics&lt;/h2&gt;
&lt;p&gt;The extension collects metrics that are printed in the
&lt;a href=&#34;/docs/k6/latest/results-output/end-of-test/&#34;&gt;end-of-test summary&lt;/a&gt; in addition to the built-in metrics.
These metrics are collected only for instant and range queries.&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;name&lt;/th&gt;
              &lt;th&gt;description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_bytes_processed_per_second&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;amount of bytes processed by Loki per second&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_bytes_processed_total&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;total amount of bytes processed by Loki&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_lines_processed_per_second&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;amount of lines processed by Loki per second&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;loki_lines_processed_total&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;total amount of lines processed by Loki&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;labels-pool&#34;&gt;Labels pool&lt;/h2&gt;
&lt;p&gt;With the xk6-loki extension, you can use the field &lt;code&gt;labels&lt;/code&gt; on the &lt;code&gt;Config&lt;/code&gt;
object. It contains label names and values that are generated in a reproducible
manner. Use the same labels cardinality configuration for both &lt;code&gt;write&lt;/code&gt; and
&lt;code&gt;read&lt;/code&gt; testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example code fragment:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;const labelCardinality = {
  &amp;#34;app&amp;#34;: 5,
  &amp;#34;namespace&amp;#34;: 2,
};
const conf = new loki.Config(BASE_URL, 10000, 1.0, labelCardinality);
const client = new loki.Client(conf);

function randomChoice(items) {
  return items[Math.floor(Math.random() * items.length)];
}

export default() {
  let app = randomChoice(conf.labels.app);
  let namespace = randomChoice(conf.labels.namespace);
  client.rangeQuery(`{app=&amp;#34;${app}&amp;#34;, namespace=&amp;#34;${namespace}&amp;#34;} | logfmt | level=&amp;#34;error&amp;#34;`, &amp;#34;15m&amp;#34;);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Alternatively, you can define your own pool of label names and values,
and then randomly select labels from your pool instead of a generated pool.&lt;/p&gt;
&lt;h2 id=&#34;javascript-example&#34;&gt;Javascript example&lt;/h2&gt;
&lt;p&gt;A more complex example of a read scenario can be found in xk6-loki repository.
The test file
&lt;a href=&#34;https://github.com/grafana/xk6-loki/blob/main/examples/read-scenario.js&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;read-scenario.js&lt;/a&gt;
can be resused and extended for your needs.&lt;/p&gt;
&lt;p&gt;It allows you to configure ratios for each type of query and the ratios of time
ranges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript example:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;const queryTypeRatioConfig = [
  {
    ratio: 0.1,
    item: readLabels
  },
  {
    ratio: 0.15,
    item: readLabelValues
  },
  {
    ratio: 0.05,
    item: readSeries
  },
  {
    ratio: 0.5,
    item: readRange
  },
  {
    ratio: 0.2,
    item: readInstant
  },
];&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This configuration would execute approximately&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10% labels requests&lt;/li&gt;
&lt;li&gt;15% label values requests&lt;/li&gt;
&lt;li&gt;5% requests for series&lt;/li&gt;
&lt;li&gt;50% range queries&lt;/li&gt;
&lt;li&gt;20% instant queries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;during a test run.&lt;/p&gt;
]]></content><description>&lt;h1 id="use-k6-to-load-test-log-queries">Use k6 to load test log queries&lt;/h1>
&lt;p>When designing a test scenario for load testing the read path of a Loki
installation, it is important to know what types of queries you expect.&lt;/p></description></item></channel></rss>