Skip to main content
Thresholds define the pass/fail criteria for your load tests. If your system doesn’t meet the threshold conditions, the test fails with a non-zero exit code. Thresholds let you codify your Service Level Objectives (SLOs) and automate performance testing.

How thresholds work

Thresholds evaluate metrics against specified conditions. Common examples include:
  • Less than 1% of requests return errors
  • 95% of requests complete within 200ms
  • 99% of requests complete within 400ms
  • Specific endpoints always respond within 300ms
When any threshold fails, k6 exits with a non-zero exit code, making thresholds essential for CI/CD integration.

Basic example

This test defines two thresholds:
import http from 'k6/http';

export const options = {
  thresholds: {
    http_req_failed: ['rate<0.01'], // HTTP errors should be less than 1%
    http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com');
}
When you run this test, k6 displays threshold results:
 THRESHOLDS

    http_req_duration
 'p(95)<200' p(95)=148.21ms

    http_req_failed
 'rate<0.01' rate=0.05%
The green checkmarks (✓) indicate passing thresholds. Failed thresholds show a red cross (✗) and cause the test to exit with code 1.

Threshold syntax

Thresholds use this format:
export const options = {
  thresholds: {
    metric_name: ['threshold_expression'],
  },
};

Threshold expressions

A threshold expression has three parts:
<aggregation_method> <operator> <value>
Examples:
  • avg < 200 - Average duration must be less than 200ms
  • count >= 500 - Count must be at least 500
  • p(90) < 300 - 90th percentile must be below 300ms

Aggregation methods by metric type

Different metric types support different aggregation methods:
thresholds: {
  my_counter: [
    'count>100',  // Total count
    'rate>0.5',   // Rate of non-zero values
  ],
}

Common threshold examples

Error rate threshold

Ensure error rate stays below 1%:
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  thresholds: {
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com');
  sleep(1);
}

Response time threshold

Ensure 90% of requests complete within 400ms:
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  thresholds: {
    http_req_duration: ['p(90) < 400'],
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com');
  sleep(1);
}

Multiple thresholds on one metric

Define different requirements for different percentiles:
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  thresholds: {
    http_req_duration: [
      'p(90) < 400',   // 90% under 400ms
      'p(95) < 800',   // 95% under 800ms
      'p(99.9) < 2000', // 99.9% under 2s
    ],
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com');
  sleep(1);
}

Thresholds with custom metrics

You can set thresholds on custom metrics:
import http from 'k6/http';
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
import { sleep } from 'k6';

const TrendRTT = new Trend('RTT');
const RateContentOK = new Rate('ContentOK');
const GaugeContentSize = new Gauge('ContentSize');
const CounterErrors = new Counter('Errors');

export const options = {
  thresholds: {
    Errors: ['count<100'],          // Less than 100 errors
    ContentSize: ['value<4000'],    // Content under 4000 bytes
    ContentOK: ['rate>0.95'],       // 95% content OK
    RTT: [
      'p(99)<300',   // 99th percentile under 300ms
      'p(70)<250',   // 70th percentile under 250ms
      'avg<200',     // Average under 200ms
      'med<150',     // Median under 150ms
      'min<100',     // Minimum under 100ms
    ],
  },
};

export default function () {
  const res = http.get('https://quickpizza.grafana.com/api/json?name=Bert');
  const contentOK = res.json('name') === 'Bert';

  TrendRTT.add(res.timings.duration);
  RateContentOK.add(contentOK);
  GaugeContentSize.add(res.body.length);
  CounterErrors.add(!contentOK);

  sleep(1);
}

Thresholds on tagged metrics

Set thresholds for specific tagged requests:
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  thresholds: {
    'http_req_duration{type:API}': ['p(95)<500'],
    'http_req_duration{type:staticContent}': ['p(95)<200'],
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com/api/headers', {
    tags: { type: 'API' },
  });
  
  http.get('https://quickpizza.grafana.com/api/json', {
    tags: { type: 'API' },
  });

  http.batch([
    ['GET', 'https://quickpizza.grafana.com/favicon.ico', null, 
     { tags: { type: 'staticContent' } }],
    ['GET', 'https://quickpizza.grafana.com/admin', null, 
     { tags: { type: 'staticContent' } }],
  ]);

  sleep(1);
}

Thresholds on groups

Set thresholds for specific groups:
import http from 'k6/http';
import { group, sleep } from 'k6';

export const options = {
  thresholds: {
    'group_duration{group:::individualRequests}': ['avg < 400'],
    'group_duration{group:::batchRequests}': ['avg < 200'],
  },
  vus: 1,
  duration: '10s',
};

export default function () {
  group('individualRequests', function () {
    http.get('https://quickpizza.grafana.com/api/json?letter=a');
    http.get('https://quickpizza.grafana.com/api/json?letter=b');
    http.get('https://quickpizza.grafana.com/api/json?letter=c');
  });

  group('batchRequests', function () {
    http.batch([
      ['GET', 'https://quickpizza.grafana.com/api/json?letter=a'],
      ['GET', 'https://quickpizza.grafana.com/api/json?letter=b'],
      ['GET', 'https://quickpizza.grafana.com/api/json?letter=c'],
    ]);
  });

  sleep(1);
}

Abort test when threshold fails

Stop the test immediately when a threshold fails:
import http from 'k6/http';

export const options = {
  vus: 30,
  duration: '2m',
  thresholds: {
    http_req_duration: [
      { 
        threshold: 'p(99) < 10',
        abortOnFail: true,
        delayAbortEval: '10s', // Wait 10s before checking
      }
    ],
  },
};

export default function () {
  http.get('https://quickpizza.grafana.com');
}
The delayAbortEval property prevents premature test abortion by waiting for enough samples to be collected.

Fail test using checks

Combine checks with thresholds to fail tests based on check results:
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '10s',
  thresholds: {
    checks: ['rate>0.9'], // 90% of checks must pass
  },
};

export default function () {
  const res = http.get('https://quickpizza.grafana.com/');

  check(res, {
    'status is 200': (r) => r.status == 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

Thresholds on specific checks

Use tags to set thresholds for specific checks:
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '10s',
  thresholds: {
    'checks{myTag:critical}': ['rate>0.99'],
  },
};

export default function () {
  const res = http.get('https://quickpizza.grafana.com/');
  
  check(
    res,
    { 'status is 200': (r) => r.status == 200 },
    { myTag: 'critical' }
  );

  sleep(1);
}

Complete example

Here’s a comprehensive example combining multiple threshold patterns:
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Trend } from 'k6/metrics';

const apiDuration = new Trend('api_duration');

export const options = {
  vus: 50,
  duration: '1m',
  thresholds: {
    // Built-in metrics
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    
    // Tagged metrics
    'http_req_duration{type:api}': ['p(95)<300'],
    'http_req_duration{type:static}': ['p(95)<100'],
    
    // Groups
    'group_duration{group:::API}': ['avg<400'],
    
    // Custom metrics
    api_duration: ['p(90)<250'],
    
    // Checks
    checks: ['rate>0.95'],
  },
};

const baseUrl = 'https://quickpizza.grafana.com';

export default function () {
  group('API', function () {
    const res = http.get(`${baseUrl}/api/pizzas`, {
      tags: { type: 'api' },
    });
    
    apiDuration.add(res.timings.duration);
    
    check(res, {
      'api status is 200': (r) => r.status === 200,
    });
  });
  
  http.get(`${baseUrl}/static/logo.png`, {
    tags: { type: 'static' },
  });
  
  sleep(1);
}

Best practices

Start with SLOs

Base thresholds on your Service Level Objectives, not arbitrary numbers.

Use multiple percentiles

Don’t rely on averages alone. Check p95, p99, and max values.

Tag for granularity

Use tags to set different thresholds for different request types.

Delay abort evaluation

Use delayAbortEval to prevent premature test abortion.

Build docs developers (and LLMs) love