k6 Custom Metric Not Recording Values Fix
In this tutorial, you'll learn about k6 custom metric not recording values fix. We cover key concepts, practical examples, and best practices.
Your k6 custom metric shows zero or no data at the end of the test — the metric is declared but add() is never called, the metric is used outside the default function, or the metric type does not match the data pattern.
The Problem
import http from 'k6/http';
import { Trend } from 'k6/metrics';
const responseSize = new Trend('response_size');
// WRONG: metric added outside the default function
const res = http.get('https://api.example.com');
responseSize.add(res.body.length);
export default function () {
// No metric calls here
}
response_size.............: 0 min=0 max=0
The add() call runs at module load time, not during VU iterations. Metrics must be recorded inside the default function.
Step-by-Step Fix
1. Record metrics inside the default function
import http from 'k6/http';
import { Trend } from 'k6/metrics';
const responseSize = new Trend('response_size');
export default function () {
const res = http.get('https://api.example.com');
responseSize.add(res.body.length);
}
2. Use Counter metrics for counts
import http from 'k6/http';
import { Counter } from 'k6/metrics';
const apiCalls = new Counter('api_calls');
const errors = new Counter('api_errors');
export default function () {
const res = http.get('https://api.example.com');
apiCalls.add(1);
if (res.status !== 200) {
errors.add(1);
}
}
3. Use Gauge metrics for point-in-time values
import http from 'k6/http';
import { Gauge } from 'k6/metrics';
const activeConnections = new Gauge('active_connections');
export default function () {
const res = http.get('https://api.example.com');
activeConnections.add(42); // Last value wins
}
4. Use Rate metrics for pass/fail ratios
import http from 'k6/http';
import { Rate } from 'k6/metrics';
const successRate = new Rate('success_rate');
export default function () {
const res = http.get('https://api.example.com');
successRate.add(res.status === 200);
}
5. Tag custom metrics for granularity
import http from 'k6/http';
import { Trend } from 'k6/metrics';
const apiLatency = new Trend('api_latency');
export const options = {
thresholds: {
'api_latency{endpoint:users}': ['p(95)<1000'],
'api_latency{endpoint:orders}': ['p(95)<3000'],
},
};
export default function () {
const users = http.get('https://api.example.com/users');
apiLatency.add(users.timings.duration, { endpoint: 'users' });
const orders = http.get('https://api.example.com/orders');
apiLatency.add(orders.timings.duration, { endpoint: 'orders' });
}
Expected output:
api_calls.................: 5000 500/s
api_errors................: 10 1/s
success_rate..............: 99.80% ✓ 4990 ✗ 10
api_latency...............: avg=145ms p(95)=320ms
Prevention Tips
- Always call
.add()inside thedefaultfunction, not at module level - Match metric type to data: Trend for durations, Counter for counts, Gauge for current values, Rate for ratios
- Use tags for metric granularity without creating separate metrics
- Set thresholds on custom metrics for CI pass/fail
- Verify metric output with a short test before full run
Common Mistakes with custom metrics
- Forgetting
deriving (Show, Eq)on custom data types needed for debugging - Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
- Using
headandtailinstead of pattern matching, causing runtime errors on empty lists
These mistakes appear frequently in real-world K6 code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro