# QA 23: Performance testing — Load, Stress, Spike, Soak va k6 bilan amaliyot

> **Holat:** 2026-yil oktabr. Dars Grafana k6 ning v2 qatori (eng so'nggi reliz — **v2.3.0**, 2026-yil 21-sentabr) va rasmiy k6 hujjatlari asosida yozilgan. Amaliy nishon — Grafana'ning o'quv ilovasi **QuickPizza**. Barcha raqamli chegaralar (Core Web Vitals, Apdex) manbalar bilan berilgan.

> **Bu darsdan keyin siz:** performance testni "tugmani bosib RPS'ni ko'rish" emas, balki muhandislik jarayoni sifatida bajara olasiz:

- latency, percentile (p95/p99), throughput va xato ulushini to'g'ri o'qiysiz;
- smoke, load, stress, spike, soak va breakpoint testlarini farqlab, vaziyatga mosini tanlaysiz;
- k6'da `stages`, `thresholds`, `scenarios`, custom metrikalar va test data bilan skript yozasiz;
- natijani tahlil qilib, jamoaga tushunarli hisobot yozasiz;
- frontend (Core Web Vitals) va mobil performance asoslarini bilasiz.

## Mundarija

1. [Performance testing nima va nega kerak](#nima-uchun)
2. [Asosiy metrikalar: latency, percentile, throughput, xatolar](#metrikalar)
3. [Little's Law va Apdex](#littles-law)
4. [Test turlari: smoke, load, stress, spike, soak, breakpoint](#test-turlari)
5. [Jarayon: SLO'dan hisobotgacha](#jarayon)
6. [k6 bilan tanishuv: o'rnatish va birinchi skript](#k6-kirish)
7. [k6 chuqurroq: checks, thresholds, scenarios, metrikalar, data](#k6-chuqur)
8. [Natijani o'qish, hisobot va CI](#natija)
9. [Boshqa vositalar: JMeter, Gatling, Locust](#boshqa-vositalar)
10. [Frontend va mobil performance](#frontend-mobil)
11. [Etika va xavfsizlik qoidalari](#etika)
12. [Amaliy mashqlar](#mashqlar)
13. [O'zingizni tekshiring](#checklist)
14. [Xulosa](#xulosa)
15. [Manbalar](#manbalar)

---

## 1. Performance testing nima va nega kerak {#nima-uchun}

**Performance testing** — tizim yuklama ostida qanchalik **tez**, **barqaror** va **kengayuvchan** (scalable) ishlashini tekshiradigan non-functional testlash turi. Funksional test "ishlaydimi?" deb so'raydi, performance test esa "**ko'p odam bir vaqtda ishlatganda ham** yetarlicha tez va xatosiz ishlaydimi?" deb so'raydi.

| Savol turi | Funksional test                                | Performance test                                                                |
| ---------- | ---------------------------------------------- | ------------------------------------------------------------------------------- |
| Misol      | "Buyurtma berish tugmasi buyurtma yaratadimi?" | "Bir daqiqada 600 ta buyurtma kelsa, 95% so'rov 1 soniyadan tez javob oladimi?" |
| Natija     | O'tdi / O'tmadi                                | Raqamlar: p95, RPS, xato ulushi, CPU, xotira                                    |
| Qayerda    | Har qanday muhit                               | Prod'ga o'xshash, alohida muhit                                                 |

**DemoBozor misoli.** Aksiya kuni soat 20:00 da barcha mijozlarga push yuboriladi: "Choynaklarga −30%". 2–3 daqiqa ichida trafik odatdagidan bir necha baravar oshadi. Nima sinishi mumkin?

- ma'lumotlar bazasi ulanishlari (connection pool) tugab qoladi → `500` va `503` xatolar;
- kesh bo'sh bo'lsa, hamma so'rov bir vaqtda bazaga uriladi (cache stampede);
- SMS (OTP) xizmati sekundiga cheklangan miqdordagi xabarni qabul qiladi → login "osilib" qoladi;
- to'lov provayderiga so'rovlar timeout bo'ladi → buyurtma `PENDING_PAYMENT` holatida qotib qoladi.

Bularni funksional testlar topmaydi — har bir so'rov alohida olinganda "to'g'ri" ishlaydi.

**QA'ning roli.** Performance testni ko'pincha QA, DevOps/SRE va dasturchilar birga qiladi. QA odatda: talablarni (NFR — non-functional requirements) aniqlashtiradi, yuklama modelini loyihalaydi, skript yozadi, natijani tahlil qilib hisobot beradi. Server ichidagi sababni topish (profiling) — ko'proq dasturchi/SRE ishi.

> **🎯 Intervyuda:** Load test va stress test farqi nima? — Load test **kutilgan** (o'rtacha yoki cho'qqi) yuklamada SLO bajarilishini tekshiradi; stress test yuklamani **kutilganidan yuqori**ga olib chiqib, tizim qayerda va qanday buzilishini hamda keyin tiklanishini o'rganadi.

---

## 2. Asosiy metrikalar: latency, percentile, throughput, xatolar {#metrikalar}

### Response time (latency) qanday tuzilgan

Bitta HTTP so'rov vaqti bir necha bo'lakdan iborat. k6 ularning har birini alohida o'lchaydi:

| Bosqich         | k6 metrikasi               | Ma'nosi                                             |
| --------------- | -------------------------- | --------------------------------------------------- |
| Navbatda kutish | `http_req_blocked`         | Bo'sh TCP ulanish kutilgan vaqt                     |
| TCP ulanish     | `http_req_connecting`      | Serverga ulanish                                    |
| TLS             | `http_req_tls_handshaking` | HTTPS "qo'l berib ko'rishish"                       |
| Yuborish        | `http_req_sending`         | So'rov tanasini yuborish                            |
| Kutish (TTFB)   | `http_req_waiting`         | Server javobining birinchi baytigacha               |
| Qabul qilish    | `http_req_receiving`       | Javobni to'liq yuklab olish                         |
| **Jami**        | `http_req_duration`        | `sending + waiting + receiving` (DNS va ulanishsiz) |

> **🧠 Esda tuting:** `http_req_waiting` (TTFB) katta bo'lsa — muammo odatda serverda (kod, baza, tashqi servis). `http_req_receiving` katta bo'lsa — javob juda og'ir (masalan, siqilmagan 5 MB JSON) yoki tarmoq sekin.

### O'rtacha qiymat nega aldaydi: percentile'lar

10 ta so'rov javob vaqti (ms): `120, 130, 125, 140, 135, 128, 132, 3000, 138, 127`.

- **O'rtacha (avg)** = 4175 / 10 = **417.5 ms**. Lekin hech bir foydalanuvchi 417 ms ni ko'rmadi: 9 kishi ~130 ms, bittasi esa 3 soniya kutdi.
- **Mediana (p50)** = **131 ms** — "odatiy" foydalanuvchi tajribasi.
- **p95** — so'rovlarning 95% shu qiymatdan tez. Ko'p namunali testda p95/p99 aynan "eng sekin 5% / 1%" foydalanuvchilarni ko'rsatadi.

Shuning uchun SLO'lar deyarli doim percentile bilan yoziladi: "`GET /products` uchun **p95 < 500 ms**", "o'rtacha < 300 ms" emas.

> **⚠️ Diqqat:** Juda tez `500` xatolar o'rtacha va p95 ni "chiroyli" qilib ko'rsatishi mumkin. Shuning uchun latency'ni har doim **xato ulushi** bilan birga baholang.

### Throughput, concurrency va xatolar

| Metrika                               | Ma'nosi                                                      | DemoBozor SLO misoli                        |
| ------------------------------------- | ------------------------------------------------------------ | ------------------------------------------- |
| **Throughput** (`http_reqs`, RPS)     | Sekundiga bajarilgan so'rovlar                               | Aksiya soatida 300 RPS ko'tara olishi kerak |
| **Iteratsiya tezligi** (`iterations`) | Sekundiga to'liq ssenariylar (masalan, "katalog → savatcha") | —                                           |
| **Concurrency** (`vus`)               | Bir vaqtdagi virtual foydalanuvchilar                        | 500 VU                                      |
| **Xato ulushi** (`http_req_failed`)   | Muvaffaqiyatsiz so'rovlar foizi                              | < 1%                                        |
| **Resurslar**                         | CPU, xotira, DB ulanishlari, navbat uzunligi                 | CPU < 75%, DB pool < 80%                    |

k6'da `http_req_failed` — kutilgan javob kodlariga mos kelmagan so'rovlar ulushi (k6 hujjatiga ko'ra bu `setResponseCallback` bilan belgilanadi). Server tomonidagi resurslarni k6 o'lchamaydi — ular uchun monitoring kerak (Grafana, Prometheus, Datadog va h.k.; batafsil: [DevOps: Observability](/ios-lessons/devops-observability)).

### Ochiq va yopiq yuklama modeli

- **Yopiq model (closed)** — VU'lar soni belgilanadi. Server sekinlashsa, har VU kamroq so'rov yuboradi → yuklama o'z-o'zidan kamayadi. Bu real hayotda ko'pincha **muammoni yashiradi**.
- **Ochiq model (open)** — sekundiga yangi kelayotgan iteratsiyalar soni belgilanadi (arrival rate). Server sekinlashsa ham yangi "foydalanuvchilar" kelaveradi — xuddi aksiya kunidagidek.

k6'da yopiq model — `constant-vus`, `ramping-vus`; ochiq model — `constant-arrival-rate`, `ramping-arrival-rate` executor'lari ([7-bo'lim](#k6-chuqur)).

---

## 3. Little's Law va Apdex {#littles-law}

### Little's Law: nechta VU kerak?

Navbat nazariyasidagi oddiy, lekin kuchli formula:

```text
L = λ × W

L — tizimdagi bir vaqtdagi "mijozlar" soni (concurrency)
λ — kelish tezligi (sekundiga so'rov yoki iteratsiya)
W — bitta mijozning tizimda o'tkazgan o'rtacha vaqti (soniya)
```

**Misol 1 (API darajasi).** DemoBozor API'ga cho'qqida 50 so'rov/s keladi, o'rtacha javob 0.4 s. Demak bir vaqtda o'rtacha `50 × 0.4 = 20` ta so'rov "ichkarida" bo'ladi. Server 20 ta parallel so'rovni ko'tara olmasa — navbat paydo bo'ladi va latency o'sadi.

**Misol 2 (VU sonini hisoblash).** Analitikaga ko'ra aksiya soatida 18 000 ta "katalog → mahsulot → savatcha" ssenariysi bajariladi: `λ = 18 000 / 3600 = 5 iteratsiya/s`. Bitta iteratsiya (so'rovlar + foydalanuvchining o'ylash vaqti `sleep`) o'rtacha 12 s davom etadi. U holda kerakli VU soni: `L = 5 × 12 = 60 VU`.

> **💡 Maslahat:** `sleep()` (think time) qo'ymasangiz, 60 VU real 60 foydalanuvchidan ko'ra ancha ko'p so'rov yuboradi va natija real hayotga o'xshamaydi. Think time qiymatini analitikadan oling (sahifalar orasidagi o'rtacha vaqt).

### Apdex: bitta raqamda "foydalanuvchi mamnunmi?"

Apdex (Application Performance Index) — 0 dan 1 gacha ball. Chegara `T` tanlanadi (masalan, 500 ms):

- **mamnun (satisfied):** javob ≤ T;
- **chidaydigan (tolerating):** T < javob ≤ 4T;
- **norozi (frustrated):** javob > 4T.

```text
Apdex_T = (mamnunlar + chidaydiganlar / 2) / jami namunalar
```

**Misol.** T = 500 ms, 1000 so'rov: 850 tasi ≤ 0.5 s, 100 tasi 0.5–2 s oralig'ida, 50 tasi > 2 s. `Apdex = (850 + 100/2) / 1000 = 0.90`.

Apdex rahbariyatga tushuntirish uchun qulay, lekin muhandislik qarorlarini baribir percentile va xato ulushiga qarab qabul qiling.

---

## 4. Test turlari: smoke, load, stress, spike, soak, breakpoint {#test-turlari}

![k6 yuklama profillari: smoke, average-load, stress, spike, soak va breakpoint testlari](/lessons/images/qa/qa_23_load_profiles.svg)

k6 hujjatidagi "test turlari" jadvali asosida (moslashtirildi):

| Tur              | VU / throughput      | Davomiyligi           | Qachon                                   | DemoBozor misoli                           |
| ---------------- | -------------------- | --------------------- | ---------------------------------------- | ------------------------------------------ |
| **Smoke**        | Past                 | Qisqa (soniya–daqiqa) | Skript yoki kod o'zgarganda              | 2 VU × 30 s: skript va muhit ishlayaptimi? |
| **Average-load** | Odatiy prod darajasi | O'rta (5–60 daq)      | Muntazam: odatiy kunda SLO bajariladimi? | Kunlik o'rtacha trafik × 30 daq            |
| **Stress**       | O'rtachadan yuqori   | O'rta (5–60 daq)      | Yuqori yuklama kutilganda                | Aksiya prognozi × 1.5                      |
| **Spike**        | Juda yuqori, keskin  | Qisqa (bir necha daq) | Mavsumiy aksiya, push, reklama           | 20:00 push'dan keyingi sakrash             |
| **Soak**         | O'rtacha             | Uzoq (soatlar)        | Uzoq ishlashdagi muammolar               | 4 soat: xotira oqishi, ulanishlar          |
| **Breakpoint**   | Sinmaguncha oshadi   | Kerakcha              | Sig'im chegarasini topish                | Yangi serverlarga ko'chishdan oldin        |

Qo'shimcha atamalar: **capacity test** (breakpoint'ga yaqin), **scalability test** (resurs qo'shilganda throughput qanchalik o'sadi), **volume test** (katta hajmdagi ma'lumot bilan, masalan 10 mln mahsulot).

> **⚠️ Diqqat:** "Smoke" so'zi ikki ma'noda ishlatiladi. [QA 4](/ios-lessons/qa-levels-types) dagi funksional smoke — "eng muhim funksiyalar ishlayaptimi?". Performance'dagi smoke — "skript va tizim minimal yuklamada to'g'ri ishlayaptimi?". Hisobotda qaysi biri ekanini aniq yozing.

k6 hujjati ikki muhim tavsiya beradi:

1. **Doim smoke'dan boshlang**, keyin average-load, so'ng boshqa turlar.
2. **Oddiy shakllar** ishlating: ramp-up → plato → ramp-down. Yuklamani ko'p marta ko'tarib-tushiradigan "rollercoaster" profillar natijani talqin qilishni qiyinlashtiradi.

> **🎯 Intervyuda:** Soak test nimani topadi? — Qisqa testda ko'rinmaydigan, vaqt o'tishi bilan to'planadigan muammolarni: xotira oqishi (memory leak), yopilmagan DB ulanishlari, disk/log to'lib qolishi, sekin degradatsiya.

---

## 5. Jarayon: SLO'dan hisobotgacha {#jarayon}

```text
1. Maqsad va SLO        →  "Nimani isbotlamoqchimiz?"
2. Yuklama modeli       →  qaysi ssenariylar, qancha, qanday nisbatda
3. Muhit va test data   →  prod'ga o'xshash, alohida, tayyorlangan
4. Monitoring           →  server metrikalari test bilan bir vaqtda
5. Bajarish             →  smoke → load → stress/spike/soak, takroriylik
6. Tahlil               →  thresholds, xatolar, percentile, bottleneck
7. Hisobot va qayta test →  topilmalar, tavsiyalar, tuzatishdan keyin qayta o'lchash
```

### 1) SLO'ni o'lchanadigan qilib yozing

Yomon talab: "Sayt tez ishlashi kerak". Yaxshi talab:

| Endpoint / ssenariy    | Yuklama | p95       | Xato ulushi |
| ---------------------- | ------- | --------- | ----------- |
| `GET /api/v1/products` | 300 RPS | < 500 ms  | < 1%        |
| `POST /api/v1/orders`  | 20 RPS  | < 1000 ms | < 0.5%      |
| Login (OTP tekshirish) | 30 RPS  | < 800 ms  | < 1%        |

### 2) Yuklama modeli (workload model)

Analitikadan oling: cho'qqi soatdagi trafik, ssenariylar nisbati, think time. DemoBozor misoli: 60% katalog va mahsulot sahifasi, 25% qidiruv, 10% savatcha, 5% buyurtma berish.

### 3) Muhit va ma'lumotlar

- Muhit prod'ga **imkon qadar o'xshash** bo'lsin: server o'lchamlari, baza hajmi, kesh sozlamalari.
- Har VU har xil foydalanuvchi va har xil mahsulotlar bilan ishlasin — aks holda hamma narsa keshdan qaytadi va natija "juda yaxshi" chiqadi.
- **To'lov provayderi, SMS shlyuz** kabi tashqi xizmatlarni stub/mock bilan almashtiring. Ularga yuklama berish — boshqa kompaniya tizimiga yuklama berish degani.

### 4) Monitoring

k6 faqat "mijoz tomondan" ko'radi. Sababni topish uchun test vaqtida server CPU/xotira, DB sekin so'rovlari, ulanishlar soni, navbatlar, GC pauzalarini ham kuzating.

### Ko'p uchraydigan xatolar

| Xato                                      | Oqibati                                                            |
| ----------------------------------------- | ------------------------------------------------------------------ |
| Yuklamani noutbukdan, Wi-Fi orqali berish | Server emas, sizning noutbuk/tarmoq "bo'g'iz" bo'lib qoladi        |
| `sleep()` yo'q                            | Real foydalanuvchiga o'xshamagan, haddan tashqari agressiv yuklama |
| Hamma VU bitta mahsulotni so'raydi        | Kesh hammasini qaytaradi, baza sinovdan o'tmaydi                   |
| Faqat latency'ga qarash                   | Tez qaytgan `500` xatolar e'tibordan chetda qoladi                 |
| Server monitoringisiz test                | "Sekin" ekanini bilasiz, lekin **nega** ekanini emas               |
| Bir marta ishlatib xulosa qilish          | Shovqin (noise) natijani buzadi — 2–3 marta takrorlang             |

---

## 6. k6 bilan tanishuv: o'rnatish va birinchi skript {#k6-kirish}

**Grafana k6** — ochiq kodli load testing vositasi. Skriptlar **JavaScript** (yoki TypeScript) da yoziladi, lekin k6 Node.js emas: u Go'da yozilgan va o'zining JS runtime'idan foydalanadi — shuning uchun ko'p npm paketlari ishlamaydi. Kuchli tomonlari: CLI-first, kodda yozilgan `thresholds` (CI uchun qulay), kam resurs bilan ko'p VU.

**Versiyalar (2026-yil oktabr):** v2.0.0 (2026-yil 11-may) eskirgan API va buyruqlarni olib tashladi, eng so'nggi reliz — **v2.3.0** (2026-yil 21-sentabr). v2 dagi o'quvchiga ta'sir qiladigan o'zgarishlar:

| v1 da                            | v2 da                     |
| -------------------------------- | ------------------------- |
| `k6 login cloud`                 | `k6 cloud login`          |
| `k6 cloud script.js`             | `k6 cloud run script.js`  |
| `--no-summary`                   | `--summary-mode=disabled` |
| `externally-controlled` executor | olib tashlangan           |
| `options.ext.loadimpact`         | `options.cloud`           |

TypeScript fayllar k6 v0.57 dan beri qo'shimcha sozlamasiz ishga tushadi.

### O'rnatish

```bash
# macOS (Homebrew)
brew install k6

# Windows
winget install k6 --source winget

# Docker
docker pull grafana/k6

# tekshirish
k6 version
```

Linux (Debian/Ubuntu, Fedora) uchun buyruqlarni rasmiy [o'rnatish sahifasidan](https://grafana.com/docs/k6/latest/set-up/install-k6/) oling — u yerda repository kalitini qo'shish qadami bor.

### Nishon: QuickPizza

QuickPizza — Grafana'ning o'quv ilovasi. Ikki variant:

```bash
# 1) Lokal (tavsiya etiladi — xohlagancha yuklama bera olasiz)
docker run --rm -it -p 3333:3333 ghcr.io/grafana/quickpizza-local:latest
# brauzerda: http://localhost:3333

# 2) Ommaviy demo: https://quickpizza.grafana.com
#    faqat KICHIK testlar uchun (bir necha VU, qisqa vaqt)
```

### Birinchi skript: smoke test

```javascript
import http from "k6/http";
import { check, sleep } from "k6";

// BASE_URL berilmasa — lokal QuickPizza (Docker)
const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";

export const options = {
  vus: 2, // 2 ta virtual foydalanuvchi
  duration: "20s", // 20 soniya davomida
};

export default function () {
  const res = http.get(`${BASE_URL}/`);
  check(res, {
    "status 200": (r) => r.status === 200,
  });
  sleep(1); // "o'ylash vaqti" (think time)
}
```

```bash
k6 run smoke.js                                              # lokal QuickPizza
k6 run -e BASE_URL=https://quickpizza.grafana.com smoke.js   # ommaviy demo (kichik!)
k6 new my-test.js                                            # tayyor shablon yaratish
```

### k6 skriptining hayot sikli

| Qism                                                | Necha marta ishlaydi            | Nima uchun                                                             |
| --------------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
| **init** (fayl tepasi: import, `options`, `open()`) | Har VU uchun bir marta          | Sozlama, fayllarni o'qish                                              |
| `setup()`                                           | Butun testda bir marta          | Muhitni tekshirish, token olish; qaytgan qiymat `default` ga uzatiladi |
| `default` (yoki `exec` funksiya)                    | Har VU qayta-qayta (iteratsiya) | Foydalanuvchi ssenariysi                                               |
| `teardown()`                                        | Butun testda bir marta          | Tozalash, xabar yuborish                                               |

> **🧠 Esda tuting:** **VU** — virtual foydalanuvchi (bitta "parallel ishchi"). **Iteratsiya** — `default` funksiyasining bir marta to'liq bajarilishi. 2 VU × 20 s, har iteratsiyada `sleep(1)` → ko'pi bilan 2 × 20 = 40 iteratsiya; amalda biroz kamroq, chunki har iteratsiyaga so'rov vaqti ham qo'shiladi.

---

## 7. k6 chuqurroq: checks, thresholds, scenarios, metrikalar, data {#k6-chuqur}

### Load test: stages + thresholds + tags + custom metrikalar

```javascript
import http from "k6/http";
import { check, group, sleep } from "k6";
import { Trend, Rate } from "k6/metrics";

const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";

// o'zimizning (custom) metrikalar
const pizzaCalories = new Trend("pizza_calories");
const vegetarianOk = new Rate("vegetarian_ok");

export const options = {
  stages: [
    { duration: "30s", target: 10 }, // ramp-up: 0 → 10 VU
    { duration: "1m", target: 10 }, // plato: 10 VU
    { duration: "30s", target: 0 }, // ramp-down
  ],
  thresholds: {
    http_req_failed: ["rate<0.01"], // xatolar < 1%
    http_req_duration: ["p(95)<500", "p(99)<1000"], // umumiy SLO
    "http_req_duration{name:PizzaAPI}": ["p(95)<800"], // faqat pizza API uchun
    checks: ["rate>0.99"],
    vegetarian_ok: ["rate==1"],
  },
};

export default function () {
  group("bosh sahifa", () => {
    const res = http.get(`${BASE_URL}/`, { tags: { name: "HomePage" } });
    check(res, { "bosh sahifa 200": (r) => r.status === 200 });
  });

  group("pizza tavsiyasi", () => {
    const payload = JSON.stringify({
      maxCaloriesPerSlice: 500,
      mustBeVegetarian: true,
      excludedIngredients: [],
      excludedTools: [],
      maxNumberOfToppings: 6,
      minNumberOfToppings: 2,
    });
    const res = http.post(`${BASE_URL}/api/pizza`, payload, {
      headers: {
        "Content-Type": "application/json",
        Authorization: "token abcdef0123456789", // QuickPizza demo token
      },
      tags: { name: "PizzaAPI" },
    });

    const ok = check(res, {
      "pizza 200": (r) => r.status === 200,
      "pizza nomi bor": (r) => r.json("pizza.name") !== undefined,
    });
    if (ok) {
      pizzaCalories.add(res.json("calories"));
      vegetarianOk.add(res.json("vegetarian") === true);
    }
  });

  sleep(1);
}
```

Muhim tushunchalar:

- **`check`** — tekshiruv, lekin **o'zi testni yiqitmaydi**. Natija `checks` metrikasiga yoziladi. Testni yiqitish uchun `checks: ['rate>0.99']` kabi threshold qo'ying.
- **`thresholds`** — SLO'ning kodga aylangan ko'rinishi. Biror threshold buzilsa, k6 **nol bo'lmagan exit code** bilan tugaydi → CI pipeline qizil bo'ladi.
- **`tags: { name: 'PizzaAPI' }`** — so'rovlarni guruhlash. `'http_req_duration{name:PizzaAPI}'` bilan faqat shu endpoint'ga alohida SLO qo'yasiz.
- **`group()`** — natijalarni mantiqiy bloklarga ajratadi (`full` summary rejimida guruh bo'yicha ko'rinadi).
- **`res.json('pizza.name')`** — JSON ichidan GJSON yo'li bilan qiymat olish.
- **Custom metrikalar:** `Counter` (yig'indi), `Gauge` (oxirgi qiymat), `Rate` (ulush), `Trend` (taqsimot: avg, p95...).

> **💡 Maslahat:** Threshold buzilganda testni darhol to'xtatish uchun: `http_req_failed: [{ threshold: 'rate<0.05', abortOnFail: true, delayAbortEval: '30s' }]`. `delayAbortEval` — boshida yetarli namuna yig'ilishini kutish.

### Scenarios va executor'lar

Bitta testda bir nechta ssenariyni turli yuklama shakllari bilan aralashtirish mumkin. k6 v2 dagi executor'lar:

| Executor                | Model | Nima qiladi                                                |
| ----------------------- | ----- | ---------------------------------------------------------- |
| `shared-iterations`     | yopiq | Belgilangan jami iteratsiyalarni VU'lar bo'lishib bajaradi |
| `per-vu-iterations`     | yopiq | Har VU aniq N marta bajaradi                               |
| `constant-vus`          | yopiq | Doimiy VU soni, belgilangan vaqt                           |
| `ramping-vus`           | yopiq | VU soni bosqichma-bosqich o'zgaradi (`stages`)             |
| `constant-arrival-rate` | ochiq | Sekundiga doimiy iteratsiya boshlanadi                     |
| `ramping-arrival-rate`  | ochiq | Iteratsiya tezligi bosqichma-bosqich o'zgaradi             |

```javascript
import http from "k6/http";
import { check, sleep } from "k6";

const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";
const HEADERS = {
  "Content-Type": "application/json",
  Authorization: "token abcdef0123456789",
};

export const options = {
  scenarios: {
    // ochiq model: sekundiga 10 ta yangi "tashrif", javob sekinlashsa ham
    browse: {
      executor: "constant-arrival-rate",
      exec: "browse",
      rate: 10,
      timeUnit: "1s",
      duration: "2m",
      preAllocatedVUs: 20,
      maxVUs: 50,
    },
    // yopiq model: 5 VU, 30 soniyadan keyin boshlanadi
    order: {
      executor: "ramping-vus",
      exec: "order",
      startTime: "30s",
      stages: [
        { duration: "30s", target: 5 },
        { duration: "1m", target: 5 },
      ],
    },
  },
  thresholds: {
    "http_req_duration{scenario:browse}": ["p(95)<400"],
    "http_req_duration{scenario:order}": ["p(95)<900"],
    http_req_failed: ["rate<0.01"],
  },
};

export function browse() {
  const res = http.get(`${BASE_URL}/`);
  check(res, { "browse 200": (r) => r.status === 200 });
}

export function order() {
  const res = http.post(
    `${BASE_URL}/api/pizza`,
    JSON.stringify({
      mustBeVegetarian: true,
      maxNumberOfToppings: 5,
      minNumberOfToppings: 2,
    }),
    { headers: HEADERS },
  );
  check(res, { "order 200": (r) => r.status === 200 });
  sleep(2);
}
```

Scenario parametrlari: `executor` (majburiy), `exec` (qaysi funksiya), `startTime`, `gracefulStop`, `env`, `tags`, `options` (masalan, brauzer sozlamalari).

### Spike test: aksiya push'i

```javascript
import http from "k6/http";
import { check } from "k6";

const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";

export const options = {
  scenarios: {
    aksiya_spike: {
      executor: "ramping-arrival-rate",
      startRate: 5, // soniyasiga 5 iteratsiya
      timeUnit: "1s",
      preAllocatedVUs: 50,
      maxVUs: 300,
      stages: [
        { target: 5, duration: "1m" }, // odatiy fon trafik
        { target: 100, duration: "10s" }, // push yuborildi — keskin sakrash
        { target: 100, duration: "1m" }, // cho'qqi
        { target: 5, duration: "10s" }, // pasayish
        { target: 5, duration: "2m" }, // tizim tiklandimi?
      ],
    },
  },
  thresholds: {
    http_req_failed: [
      { threshold: "rate<0.05", abortOnFail: true, delayAbortEval: "30s" },
    ],
    http_req_duration: ["p(95)<1500"],
  },
};

export default function () {
  const res = http.get(`${BASE_URL}/`);
  check(res, { "status 200": (r) => r.status === 200 });
}
```

> **⚠️ Diqqat:** Bu skript soniyasiga 100 iteratsiyagacha yuklama beradi — uni **faqat lokal QuickPizza** yoki o'z test muhitingizga qarshi ishlating. Ommaviy demo saytlar kichik testlar uchun mo'ljallangan.

Oxiridagi 2 daqiqalik "tinch" bosqich muhim: spike'dan keyin tizim **o'zi tiklanadimi** (latency normal holatga qaytadimi, xatolar to'xtaydimi) — buni ham tekshiramiz.

### Soak va breakpoint profillari

```javascript
// Soak: 4 soat o'rtacha yuklama
export const soakOptions = {
  stages: [
    { duration: "5m", target: 50 },
    { duration: "4h", target: 50 },
    { duration: "5m", target: 0 },
  ],
};

// Breakpoint: sinmaguncha oshiramiz, xatolar 5% dan oshsa — to'xtatamiz
export const breakpointOptions = {
  scenarios: {
    breakpoint: {
      executor: "ramping-arrival-rate",
      preAllocatedVUs: 100,
      maxVUs: 2000,
      stages: [{ duration: "30m", target: 2000 }],
    },
  },
  thresholds: {
    http_req_failed: [{ threshold: "rate<0.05", abortOnFail: true }],
    http_req_duration: [{ threshold: "p(95)<2000", abortOnFail: true }],
  },
};
```

(Real skriptda bularning birini `export const options = ...` sifatida ishlatasiz.)

### Test data: SharedArray va setup()

```javascript
import http from "k6/http";
import { check, sleep } from "k6";
import { SharedArray } from "k6/data";
import exec from "k6/execution";

const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";

// open() faqat init qismida ishlaydi; SharedArray xotirani VU'lar orasida bo'lishadi
const tokens = new SharedArray("tokens", function () {
  return JSON.parse(open("./data/tokens.json")).tokens;
});

export const options = { vus: 5, duration: "30s" };

export function setup() {
  const res = http.get(BASE_URL);
  if (res.status !== 200) {
    throw new Error(`Muhit tayyor emas: status ${res.status}`);
  }
  return { startedAt: new Date().toISOString() }; // default() ga uzatiladi
}

export default function (data) {
  // har bir VU test oxirigacha bitta "foydalanuvchi" bo'lib qoladi
  const token = tokens[(exec.vu.idInTest - 1) % tokens.length];
  const res = http.post(
    `${BASE_URL}/api/pizza`,
    JSON.stringify({ mustBeVegetarian: true }),
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: `token ${token}`,
      },
    },
  );
  check(res, { "pizza 200": (r) => r.status === 200 });
  sleep(1);
}

export function teardown(data) {
  console.log(`Test ${data.startedAt} da boshlangan edi`);
}
```

`data/tokens.json`:

```json
{
  "tokens": ["loadtestuser0004", "loadtestuser0005", "loadtestuser0006"]
}
```

> **🧠 Esda tuting:** `open()` faqat **init** qismida ishlaydi (`default` ichida emas). Katta fayllarni `SharedArray` bilan o'qing — aks holda har VU faylning o'z nusxasini xotirada saqlaydi.

### k6 browser: frontend metrikalari bilan

k6 da `k6/browser` moduli bor — haqiqiy Chromium brauzerida sahifani ochib, Web Vitals (LCP, CLS, INP, FCP, TTFB) ni o'lchaydi. Kompyuterda Chromium asosidagi brauzer (masalan, Google Chrome) bo'lishi kerak.

```javascript
import { browser } from "k6/browser";
import { check } from "k6";

const BASE_URL = __ENV.BASE_URL || "http://localhost:3333";

export const options = {
  scenarios: {
    ui: {
      executor: "shared-iterations",
      vus: 1,
      iterations: 3,
      options: { browser: { type: "chromium" } },
    },
  },
  thresholds: {
    browser_web_vital_lcp: ["p(75)<2500"],
    browser_web_vital_cls: ["p(75)<0.1"],
  },
};

export default async function () {
  const page = await browser.newPage();
  try {
    await page.goto(BASE_URL);
    await page.locator('button[name="pizza-please"]').click();
    const title = await page.locator("h1").textContent();
    check(title, { "sarlavha bor": (t) => t !== null && t.length > 0 });
  } finally {
    await page.close();
  }
}
```

Odatiy amaliyot: **ko'p protokol (HTTP) VU + bir nechta brauzer VU** — backend yuklama ostida bo'lganda foydalanuvchi sahifani qanday his qilishini ko'rish. k6 da Playwright uslubidagi `expect` assertion'lari ham bor (k6-testing kutubxonasi), lekin u hozircha **preview** holatida.

---

## 8. Natijani o'qish, hisobot va CI {#natija}

### End-of-test summary

k6 v2 da summary standart holatda **compact** rejimda chiqadi (`--summary-mode=full` — guruh va ssenariylar bo'yicha batafsil). Quyida rasmiy k6 hujjatidagi compact namunadan qisqartirilgan parcha:

```text
  █ THRESHOLDS

    http_req_duration
    ✓ 'p(95)<1500' p(95)=148.21ms
    ✓ 'p(90)<2000' p(90)=146.88ms

    http_req_failed
    ✓ 'rate<0.01' rate=0.00%


  █ TOTAL RESULTS

    checks_total.......................: 90      13.122179/s
    checks_succeeded...................: 100.00% 90 out of 90
    checks_failed......................: 0.00%   0 out of 90

    HTTP
    http_req_duration..................: avg=140.36ms   min=119.08ms med=140.96ms max=154.63ms p(90)=146.88ms p(95)=148.21ms
      { expected_response:true }.......: avg=140.36ms   min=119.08ms med=140.96ms max=154.63ms p(90)=146.88ms p(95)=148.21ms
    http_req_failed....................: 0.00%  0 out of 45
    http_reqs..........................: 45     6.56109/s

    EXECUTION
    iteration_duration.................: avg=152.38ms   min=119.37ms med=141.27ms max=684.62ms p(90)=147.11ms p(95)=148.39ms
    iterations.........................: 45     6.56109/s
    vus................................: 1      min=1       max=1
```

Qanday o'qiladi:

1. **THRESHOLDS** — eng birinchi qaraladigan joy. `✓` — SLO bajarildi, `✗` — buzildi.
2. **checks** — funksional to'g'rilik: yuklama ostida javoblar hali ham to'g'rimi?
3. **http_req_duration** — `med` (p50) va `p(95)` ni solishtiring. Ular orasidagi katta farq — "dum" (tail latency) muammosi.
4. **`{ expected_response:true }`** qatori — faqat muvaffaqiyatli javoblar vaqti. Asosiy qator bilan farq qilsa, xatoli javoblar vaqtni buzayotgan bo'ladi.
5. **http_req_failed** va **http_reqs** — xato ulushi va throughput (sekundiga so'rov).
6. **vus** — haqiqatda nechta VU ishlagan (arrival-rate'da `maxVUs` yetmasa, k6 ogohlantiradi).

### Bottleneck'ni qidirish

| Belgi                                                | Ehtimoliy sabab                     | Nimani tekshirish                                           |
| ---------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------- |
| Yuklama oshgani sari p95 keskin o'sadi, RPS o'smaydi | Sig'im tugadi ("tizza" nuqtasi)     | CPU, DB ulanishlari, thread pool                            |
| Ma'lum vaqtdan keyin `503`/timeout'lar               | Connection pool yoki navbat to'ldi  | Pool o'lchami, navbat uzunligi                              |
| Soak testda latency asta-sekin o'sadi                | Xotira oqishi, yopilmagan resurslar | Xotira grafigi, GC, ulanishlar soni                         |
| Faqat bitta endpoint sekin                           | Sekin SQL, N+1 so'rov, indeks yo'q  | DB slow query log, `EXPLAIN` ([QA 12](/ios-lessons/qa-sql)) |
| `http_req_waiting` kichik, `receiving` katta         | Javob juda og'ir                    | Payload hajmi, siqish (gzip/br), pagination                 |
| Lokal tez, CI'da sekin                               | Load generator'ning o'zi bo'g'iz    | Generator CPU/tarmog'i                                      |

### Natijalarni saqlash va vizualizatsiya

```bash
k6 run --out json=results.json load.js         # har bir nuqta JSON faylga
K6_WEB_DASHBOARD=true k6 run load.js           # jonli dashboard: http://127.0.0.1:5665
K6_WEB_DASHBOARD=true K6_WEB_DASHBOARD_EXPORT=report.html k6 run load.js  # HTML hisobot
```

Real vaqtda metrikalarni Grafana Cloud k6, Prometheus remote write, InfluxDB, OpenTelemetry va boshqa tizimlarga ham uzatish mumkin.

### Hisobot shabloni

```text
# Performance test hisoboti — DemoBozor aksiya tayyorgarligi

Maqsad:      Aksiya kuni 300 RPS da katalog SLO'si bajariladimi?
Muhit:       staging-perf (prod bilan bir xil instance turi), DB: 2 mln mahsulot
Versiya:     api v2.14.0, build 1842
Yuklama:     ramping-arrival-rate 0→300 it/s 10 daq, 300 it/s da 30 daq
Natijalar:
  | Metrika                         | SLO       | Natija   | Holat |
  | GET /products p95               | < 500 ms  | 430 ms   | OK    |
  | POST /orders p95                | < 1000 ms | 1840 ms  | FAIL  |
  | Xato ulushi                     | < 1%      | 0.4%     | OK    |
Topilmalar:  POST /orders da DB lock kutish; 250+ it/s da pool 100% band
Tavsiyalar:  orders jadvaliga indeks, pool 20→40, qayta test
Ilovalar:    report.html, Grafana dashboard havolasi, skript commit'i
```

> **💡 Maslahat:** Hisobotda faqat "yiqildi/o'tdi" emas, **qaysi yuklamada** buzilish boshlanganini yozing ("230 it/s gacha SLO bajarildi, 250 dan keyin p95 2 baravar o'sdi"). Bu jamoaga sig'imni rejalashtirishga yordam beradi.

### CI'da performance smoke

Har PR'da og'ir load test kerak emas, lekin qisqa smoke perf test regressiyani erta ushlaydi. GitHub Actions uchun Grafana'ning rasmiy action'lari bor:

```yaml
name: perf-smoke
on:
  pull_request:

jobs:
  k6:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: grafana/setup-k6-action@v1
      - uses: grafana/run-k6-action@v1
        with:
          path: ./perf/smoke.js
          flags: -e BASE_URL=${{ vars.STAGING_URL }}
```

Threshold buzilsa, k6 nol bo'lmagan kod bilan chiqadi va job qizil bo'ladi — bu **quality gate** vazifasini bajaradi. Og'ir testlarni (stress, soak) esa kechasi (`schedule`) yoki reliz oldidan alohida ishlating. CI haqida batafsil: [QA 21](/ios-lessons/qa-ci-cd).

---

## 9. Boshqa vositalar: JMeter, Gatling, Locust {#boshqa-vositalar}

| Vosita            | Skript tili                                | Kuchli tomoni                             | E'tibor bering                                       |
| ----------------- | ------------------------------------------ | ----------------------------------------- | ---------------------------------------------------- |
| **k6**            | JavaScript / TypeScript                    | CLI-first, thresholds-as-code, kam resurs | Node.js emas — npm paketlari cheklangan              |
| **Apache JMeter** | GUI'da test plan (`.jmx`, XML)             | Ko'p protokol, katta plagin ekotizimi     | Yuklamani GUI'dan emas, CLI (non-GUI) rejimda bering |
| **Gatling**       | Java, Scala, Kotlin, JavaScript/TypeScript | Kuchli DSL, chiroyli HTML hisobot         | JVM asosida ishlaydi                                 |
| **Locust**        | Python                                     | Python bilan oson, web UI                 | Python bilmaydigan jamoaga mos emas                  |

JMeter'da yuklama testi va HTML hisobotni CLI'dan ishga tushirish (rasmiy hujjatdagi format):

```bash
jmeter -n -t test-plan.jmx -l results.jtl -e -o report/
```

Locust'dagi eng oddiy fayl (`locustfile.py`, rasmiy quickstart'dagi misolga o'xshash):

```python
from locust import HttpUser, task

class KatalogUser(HttpUser):
    @task
    def katalog(self):
        self.client.get("/")
```

`locust` buyrug'idan keyin web UI `http://localhost:8089` da ochiladi.

> **💡 Maslahat:** Vositani jamoa tiliga qarab tanlang: frontend/JS jamoa — k6, Java jamoa — Gatling yoki JMeter, Python jamoa — Locust. Tushunchalar (SLO, percentile, ochiq/yopiq model) hammasida bir xil.

---

## 10. Frontend va mobil performance {#frontend-mobil}

Backend 200 ms da javob bersa ham, foydalanuvchi sahifani 5 soniyada ko'rishi mumkin: og'ir rasmlar, bloklovchi JavaScript, kech yuklanadigan shriftlar.

### Core Web Vitals (web.dev)

| Metrika                             | Nimani o'lchaydi                  | "Yaxshi" chegara |
| ----------------------------------- | --------------------------------- | ---------------- |
| **LCP** (Largest Contentful Paint)  | Asosiy kontent ko'rinish tezligi  | ≤ 2.5 s          |
| **INP** (Interaction to Next Paint) | Bosish/yozishga javob tezligi     | ≤ 200 ms         |
| **CLS** (Cumulative Layout Shift)   | Sahifa elementlarining "sakrashi" | ≤ 0.1            |

Chegaralar sahifa yuklanishlarining **75-percentile**'ida, mobil va desktop uchun alohida baholanadi. INP 2024-yilda FID o'rniga Core Web Vital bo'ldi.

QA uchun amaliy tekshiruvlar (DemoBozor mahsulot sahifasi):

- Chrome DevTools → **Lighthouse** — lab o'lchovi; **Performance** paneli — nima sekin ekanini ko'rish ([QA 9](/ios-lessons/qa-web-http-devtools));
- Network throttling (masalan, "Slow 4G") bilan sahifani ochib ko'ring;
- narx yoki banner kech yuklanib, tugmalarni pastga "itarib yuborsa" — bu CLS bug'i;
- rasm o'lchamlari, lazy loading, keshlash header'lari.

> **🧠 Esda tuting:** **Lab data** (Lighthouse) — bitta qurilmada, boshqariladigan sharoitda. **Field data** (real foydalanuvchilar, RUM) — haqiqiy qurilma va tarmoqlar. Ikkalasi ham kerak.

### Mobil ilovalar

| Nima                    | Qanday tekshiriladi                                                                   |
| ----------------------- | ------------------------------------------------------------------------------------- |
| Cold/warm start vaqti   | Ilovani to'liq yopib ochish; Xcode Instruments, Android Studio Profiler               |
| Xotira va oqishlar      | Instruments (Allocations, Leaks), Android Profiler; uzoq ishlatish ssenariysi         |
| Kadrlar tushishi (jank) | Uzun ro'yxatlarni scroll qilish, animatsiyalar                                        |
| Batareya va tarmoq      | Fon rejimidagi so'rovlar, keraksiz polling                                            |
| Sekin tarmoq            | iOS Network Link Conditioner, [Proxyman Network Conditions](/ios-lessons/qa-proxyman) |

Mobil testlash bo'yicha to'liq checklist — [QA 14](/ios-lessons/qa-mobile-testing).

---

## 11. Etika va xavfsizlik qoidalari {#etika}

Load test — bu ataylab katta trafik yaratish. Noto'g'ri nishonga qaratilsa, u **DoS hujumidan farq qilmaydi**.

1. **Faqat o'zingizga tegishli yoki yozma ruxsat olingan tizimlarni** testlang. Boshqa kompaniya saytini "qiziqish uchun" yuklama bilan tekshirish — qonunbuzarlik bo'lishi mumkin.
2. Umumiy (shared) muhitda test qilishdan oldin **DevOps va navbatchi jamoani ogohlantiring**, vaqtni kelishing.
3. **Tashqi xizmatlarni** (to'lov, SMS, xaritalar) stub qiling — ularning limitlari va narxlari bor.
4. Har doim **to'xtatish mezoni** bo'lsin: `abortOnFail`, maksimal VU, maksimal davomiylik.
5. Prod'da test faqat alohida kelishuv, monitoring va reja bilan (masalan, past trafikli soatda).
6. Real foydalanuvchi ma'lumotlarini emas, **test data**'dan foydalaning.
7. Ommaviy demo nishonlar (masalan, quickpizza.grafana.com) — faqat **kichik** o'quv testlari uchun; katta yuklama uchun lokal nusxani ishga tushiring.

---

## 12. Amaliy mashqlar {#mashqlar}

### Mashq 1: O'rtacha vs mediana

Qidiruv endpoint'ining 10 ta javob vaqti (ms): `200, 210, 190, 205, 2400, 198, 202, 215, 207, 193`. O'rtacha va medianani hisoblang. Qaysi biri foydalanuvchi tajribasini yaxshiroq ifodalaydi va SLO'ni qanday yozgan bo'lardingiz?

<details>
<summary>💡 Javobni ko'rish</summary>

- Yig'indi: 200+210+190+205+2400+198+202+215+207+193 = **4220** → o'rtacha **422 ms**.
- Saralangan: 190, 193, 198, 200, 202, 205, 207, 210, 215, 2400 → mediana = (202 + 205) / 2 = **203.5 ms**.

Mediana "odatiy" foydalanuvchini ko'rsatadi; o'rtacha bitta 2.4 s lik so'rov tufayli ikki baravar oshib ketgan. SLO percentile bilan yoziladi: masalan, "`GET /search` uchun p95 < 500 ms va xato ulushi < 1%" — shunda sekin "dum" ham nazoratda bo'ladi.

</details>

### Mashq 2: Qaysi test turi?

Har bir vaziyat uchun test turini tanlang:

1. Yangi k6 skript yozildi, uni birinchi marta ishga tushiryapsiz.
2. Server 6 soat ishlagandan keyin sekinlashib qolayotganidan shikoyat bor.
3. "Hayit aksiyasi" push xabari 2 mln foydalanuvchiga bir vaqtda yuboriladi.
4. Yangi DB serverga ko'chishdan oldin "maksimum nechta buyurtma/s ko'taramiz?" degan savol.
5. Har haftalik reliz: oddiy kunlik trafikda SLO buzilmadimi?

<details>
<summary>💡 Javobni ko'rish</summary>

1. **Smoke** — skript va muhit ishlashini minimal yuklamada tekshirish.
2. **Soak** — uzoq vaqtli o'rtacha yuklama: xotira oqishi, yopilmagan ulanishlar.
3. **Spike** — qisqa vaqtda keskin sakrash va undan keyingi tiklanish.
4. **Breakpoint** (capacity) — sinmaguncha oshirib, chegarani topish.
5. **Average-load** — odatiy yuklamada regressiya bormi.

</details>

### Mashq 3: Little's Law bilan VU hisoblash

Prognoz: aksiya soatida daqiqasiga 120 ta buyurtma ssenariysi. Bitta ssenariy: so'rovlar jami ~2 s + foydalanuvchining o'ylash vaqti 28 s. Nechta VU kerak? Agar `sleep()` ni olib tashlasangiz, shu throughput uchun nechta VU yetadi va bu nimani anglatadi?

<details>
<summary>💡 Javobni ko'rish</summary>

- λ = 120 / 60 = **2 iteratsiya/s**; W = 2 + 28 = **30 s** → L = 2 × 30 = **60 VU**.
- `sleep()` siz: W ≈ 2 s → L = 2 × 2 = **4 VU** xuddi shu 2 it/s ni beradi.

Xulosa: think time'siz test real concurrency'ni (bir vaqtdagi sessiyalar, ochiq ulanishlar, xotiradagi sessiyalar) ancha **kam** ko'rsatadi. Real hayotga yaqin model uchun think time'ni analitikadan oling.

</details>

### Mashq 4: Thresholds yozing

SLO: (a) barcha so'rovlarda xato < 0.5%; (b) `name:Checkout` tegli so'rovlar uchun p95 < 800 ms va p99 < 1500 ms; (c) check'larning 99% dan ko'prog'i o'tsin; (d) xato ulushi 10% dan oshsa, 20 soniyalik "isinish"dan keyin test darhol to'xtasin. `options.thresholds` ni yozing.

<details>
<summary>💡 Javobni ko'rish</summary>

```javascript
export const options = {
  thresholds: {
    http_req_failed: [
      "rate<0.005",
      { threshold: "rate<0.10", abortOnFail: true, delayAbortEval: "20s" },
    ],
    "http_req_duration{name:Checkout}": ["p(95)<800", "p(99)<1500"],
    checks: ["rate>0.99"],
  },
};
```

Bitta metrikaga bir nechta threshold berish mumkin: oddiy satr ko'rinishi ham, `abortOnFail` li obyekt ham bir massivda turishi mumkin. So'rovlarga `tags: { name: 'Checkout' }` qo'yishni unutmang.

</details>

### Mashq 5: Summary'ni talqin qiling

Quyidagi — **o'quv misoli** (raqamlar xayoliy). Nima bo'lgan va keyingi qadamingiz qanday?

```text
  █ THRESHOLDS

    http_req_duration
    ✗ 'p(95)<500' p(95)=912.40ms

    http_req_failed
    ✗ 'rate<0.01' rate=3.20%

  █ TOTAL RESULTS

    HTTP
    http_req_duration..................: avg=310.11ms min=95.20ms med=180.33ms max=30.01s p(90)=640.12ms p(95)=912.40ms
      { expected_response:true }.......: avg=205.47ms min=95.20ms med=176.90ms max=1.20s  p(90)=420.55ms p(95)=488.02ms
    http_req_failed....................: 3.20%  154 out of 4800
    http_reqs..........................: 4800   39.6/s
```

<details>
<summary>💡 Javobni ko'rish</summary>

- Ikkala threshold buzilgan → test **FAIL**, CI qizil.
- Muvaffaqiyatli javoblar (`expected_response:true`) p95 = 488 ms — ya'ni SLO'ga deyarli mos. Umumiy p95 ni **xatoli javoblar** buzmoqda.
- `max=30.01s` — timeout'ga o'xshaydi: 3.2% (154 ta) so'rov uzoq kutib, xato bilan tugagan.
- Keyingi qadamlar: xatolar qaysi endpoint va qaysi status kodida ekanini aniqlash (tag'lar, `--out json`), server loglari va monitoringda shu vaqtdagi CPU/DB pool/timeout'larni ko'rish, kichikroq yuklamada qayta ishga tushirib, buzilish qaysi nuqtada boshlanishini topish.

</details>

### Mashq 6: Spike ssenariysini yozing

Talab: 2 daqiqa davomida soniyasiga 10 iteratsiya (fon), keyin 15 soniyada 150 it/s gacha sakrash, 1 daqiqa ushlab turish, 15 soniyada 10 ga qaytish va yana 3 daqiqa kuzatish. Executor va `stages` ni yozing.

<details>
<summary>💡 Javobni ko'rish</summary>

```javascript
export const options = {
  scenarios: {
    spike: {
      executor: "ramping-arrival-rate",
      startRate: 10,
      timeUnit: "1s",
      preAllocatedVUs: 100,
      maxVUs: 500,
      stages: [
        { target: 10, duration: "2m" },
        { target: 150, duration: "15s" },
        { target: 150, duration: "1m" },
        { target: 10, duration: "15s" },
        { target: 10, duration: "3m" },
      ],
    },
  },
};
```

Ochiq model (`ramping-arrival-rate`) tanlandi: server sekinlashsa ham yangi foydalanuvchilar kelishda davom etadi — haqiqiy push'dagi kabi. `maxVUs` yetmasa, k6 ogohlantirish beradi — demak generator'ga ko'proq VU kerak.

</details>

### Mashq 7: Performance bug report

Stress testda `POST /api/v1/orders` 250 it/s dan keyin p95 3.1 s ga chiqdi va 4% `503` qaytdi. Bug report'da qaysi ma'lumotlar bo'lishi kerak? ([QA 8](/ios-lessons/qa-bug-reports) formatini eslang.)

<details>
<summary>💡 Javobni ko'rish</summary>

- **Sarlavha:** "`POST /api/v1/orders`: 250 it/s dan yuqori yuklamada p95 3.1 s va 4% 503 (SLO: p95 < 1 s, xato < 0.5%)".
- **Muhit:** staging-perf, API versiyasi/build, DB hajmi, instance turlari.
- **Qayta hosil qilish:** skript fayli (commit havolasi), aniq `options` (executor, stages), ishga tushirish buyrug'i.
- **Kutilgan/haqiqiy natija:** SLO jadvali bilan solishtirish.
- **Dalillar:** k6 summary, HTML hisobot, Grafana dashboard skrinshotlari (CPU, DB pool, xatolar) — vaqt oralig'i bilan.
- **Kuzatuv:** buzilish qaysi yuklamadan boshlangani, 503 lar qaysi komponentdan kelgani (gateway yoki servis).
- **Jiddiylik/ustuvorlik:** aksiya sanasiga bog'liq holda (aksiyagacha muddat qisqa bo'lsa — yuqori).

</details>

---

## 13. O'zingizni tekshiring {#checklist}

- [ ] Nega SLO o'rtacha emas, percentile bilan yozilishini tushuntira olaman
- [ ] Little's Law bilan kerakli VU sonini hisoblay olaman
- [ ] 6 ta test turini va ularning maqsadini ayta olaman
- [ ] Ochiq va yopiq yuklama modelining farqini bilaman
- [ ] k6'da `stages`, `thresholds`, `scenarios`, `tags` va custom metrika yoza olaman
- [ ] Summary'ni o'qib, keyingi qadamni belgilay olaman
- [ ] Core Web Vitals chegaralarini bilaman
- [ ] Load test etikasi qoidalariga amal qilaman

---

## Xulosa {#xulosa}

- Performance testing — tezlik, barqarorlik va sig'imni o'lchash; funksional testlar topmaydigan muammolarni topadi.
- SLO'larni **percentile** (p95/p99) va **xato ulushi** bilan yozing; o'rtacha qiymat aldaydi.
- Little's Law (`L = λ × W`) VU sonini hisoblashga, Apdex natijani biznes tiliga o'girishga yordam beradi.
- Har doim **smoke**dan boshlang; keyin average-load, stress, spike, soak, breakpoint — maqsadga qarab.
- k6 v2: `thresholds` SLO'ni kodga aylantiradi va CI'da quality gate bo'ladi; ochiq model uchun arrival-rate executor'larini ishlating.
- k6 faqat mijoz tomonini ko'radi — sababni topish uchun server monitoringi shart.
- Foydalanuvchi tezligi faqat backend emas: Core Web Vitals (LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1) va mobil start/xotira ham muhim.
- **Keyingi dars:** [QA 24: Security testing asoslari — OWASP Top 10:2025, IDOR, injection va QA xavfsizlik checklisti](/ios-lessons/qa-security)

## Manbalar {#manbalar}

1. [Grafana k6 — rasmiy hujjatlar](https://grafana.com/docs/k6/latest/)
2. [grafana/k6 — GitHub releases](https://github.com/grafana/k6/releases)
3. [k6 v2.0.0 reliz eslatmalari (breaking changes)](https://github.com/grafana/k6/releases/tag/v2.0.0)
4. [k6 — Install k6](https://grafana.com/docs/k6/latest/set-up/install-k6/)
5. [k6 — Load test types](https://grafana.com/docs/k6/latest/testing-guides/test-types/)
6. [k6 — Thresholds](https://grafana.com/docs/k6/latest/using-k6/thresholds/)
7. [k6 — Executors](https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/)
8. [k6 — Built-in metrics](https://grafana.com/docs/k6/latest/using-k6/metrics/reference/)
9. [k6 — End of test summary](https://grafana.com/docs/k6/latest/results-output/end-of-test/)
10. [k6 — Web dashboard](https://grafana.com/docs/k6/latest/results-output/web-dashboard/)
11. [k6 — Using k6 browser](https://grafana.com/docs/k6/latest/using-k6-browser/)
12. [grafana/quickpizza — GitHub](https://github.com/grafana/quickpizza)
13. [grafana/run-k6-action — GitHub](https://github.com/grafana/run-k6-action)
14. [web.dev — Web Vitals](https://web.dev/articles/vitals)
15. [Wikipedia — Apdex](https://en.wikipedia.org/wiki/Apdex)
16. [Wikipedia — Little's law](https://en.wikipedia.org/wiki/Little%27s_law)
17. [Apache JMeter — Generating Report Dashboard](https://jmeter.apache.org/usermanual/generating-dashboard.html)
18. [Gatling — JavaScript/TypeScript installation guide](https://docs.gatling.io/tutorials/test-as-code/javascript/installation-guide/)
19. [Locust — Quickstart](https://docs.locust.io/en/stable/quickstart.html)
