Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

JMeter advanced — JSR223 scripting (Groovy)

Performance Testing with JMeter and k6 Bài 41/60

Mở đầu — vì sao bài này quan trọng

Đến bài này, bạn đã biết cách dùng JMeter ở mức "kéo-thả": thêm Thread Group, cấu hình HTTP Sampler, dùng CSV Data Set, đặt Assertion và Timer. Với 80% kịch bản load test thông thường, chừng đó là đủ. Nhưng ngay khi bạn gặp một yêu cầu "hơi lạ" — ký chữ ký HMAC cho mỗi request, sinh một payload JSON động phụ thuộc vào response trước đó, tính toán một token theo thuật toán riêng của ngân hàng, hay ghép nối dữ liệu từ ba nguồn khác nhau — thì các component có sẵn sẽ bó tay. Đó là lúc bạn phải "viết code" ngay bên trong JMeter.

Cơ chế để làm điều đó là JSR223. Đây là tên một chuẩn của Java (JSR nghĩa là Java Specification Request, số 223) cho phép chạy ngôn ngữ scripting bên trong JVM. Trong JMeter, JSR223 xuất hiện dưới dạng nhiều component: JSR223 Sampler, JSR223 PreProcessor, JSR223 PostProcessor, JSR223 Assertion, JSR223 Timer, JSR223 Listener. Và ngôn ngữ bạn nên chọn để viết là Groovy.

Tại sao bài này quan trọng đối với một Performance Engineer? Vì scripting là ranh giới phân biệt người "dùng công cụ" với người "làm chủ công cụ". Khi hệ thống bạn test có logic bảo mật hoặc business phức tạp — mà ở Việt Nam thì cổng thanh toán, ví điện tử, ngân hàng số đều như vậy — bạn không thể chỉ gửi request tĩnh. Bạn phải mô phỏng đúng cách client thật tạo request. JSR223 + Groovy chính là con dao đa năng để làm việc đó. Nhưng nó cũng là con dao dễ tự làm bị thương: script viết sai cách có thể khiến chính JMeter trở thành bottleneck, làm sai lệch toàn bộ kết quả đo. Bài này dạy bạn cả hai mặt: dùng được, và dùng đúng.

Khái niệm cốt lõi

BeanShell đã chết, Groovy lên ngôi

Nếu bạn đọc các tài liệu JMeter cũ (trước 2016), bạn sẽ thấy khắp nơi nhắc đến BeanShell. BeanShell là engine scripting mặc định của JMeter thời kỳ đầu. Vấn đề của nó rất nghiêm trọng với performance testing: BeanShell thông dịch (interpret) lại script từng dòng cho MỖI lần thực thi, không biên dịch trước, không cache. Khi bạn chạy 1.000 luồng ảo, mỗi luồng gọi một BeanShell PostProcessor, JMeter phải parse lại đoạn code đó hàng chục nghìn lần mỗi giây. CPU của máy load generator bị đốt cháy cho việc parse script thay vì cho việc sinh tải. Kết quả: throughput bạn đo được thấp giả tạo, còn máy tạo tải thì nghẽn trước cả server.

Groovy khác hẳn. Groovy là ngôn ngữ chạy trên JVM, cú pháp gần giống Java nhưng gọn hơn nhiều, và quan trọng nhất: JMeter có thể biên dịch (compile) script Groovy thành bytecode một lần rồi cache lại, các lần chạy sau chỉ việc thực thi bytecode đã compile. Nhanh hơn BeanShell hàng chục lần. Đây là lý do tài liệu chính thức của JMeter khuyến nghị rõ ràng: dùng JSR223 với Groovy cho mọi nhu cầu scripting, và tránh BeanShell trừ khi bắt buộc phải tương thích ngược.

Từ khóa để bật cache: khi bạn dùng bất kỳ JSR223 element nào, hãy điền một giá trị vào ô Cache compiled script if available (mặc định đã tick). Với script viết trực tiếp trong ô Script, JMeter tự cache. Với script nạp từ file bên ngoài, bạn nên điền một chuỗi bất kỳ vào ô "Parameters"-liền-kề hoặc để tên file ổn định để cache có hiệu lực.

Các loại JSR223 element và vị trí trong vòng đời request

Điều nhiều người mới nhầm là nghĩ "JSR223 chỉ là một chỗ để viết code". Thực ra vị trí bạn đặt nó quyết định code chạy khi nào:

  • JSR223 PreProcessor: chạy ngay trước một Sampler. Dùng để chuẩn bị dữ liệu — sinh timestamp, ký request, build body JSON động, đặt biến trước khi gửi.
  • JSR223 PostProcessor: chạy ngay sau một Sampler nhận được response. Dùng để trích xuất và xử lý dữ liệu trả về mà Regex/JSON Extractor thông thường không làm nổi — ví dụ parse JSON lồng nhau rồi tính toán.
  • JSR223 Sampler: bản thân nó một request/hành động. Không nhất thiết phải là HTTP; bạn có thể dùng để gọi một thư viện Java, tính toán nặng, hoặc mô phỏng một tác vụ.
  • JSR223 Assertion: kiểm tra response theo logic tùy ý và đánh dấu pass/fail.
  • JSR223 Timer: tính toán thời gian chờ động giữa các request.
  • JSR223 PostProcessor/Listener: xử lý hoặc ghi log kết quả tùy biến.

Những biến "phép thuật" có sẵn (bound variables)

Khi viết Groovy trong JSR223, JMeter tự động cung cấp cho bạn một loạt object gắn sẵn. Đây là phần bạn phải thuộc lòng:

  • vars — kho biến của thread hiện tại (JMeterVariables). Dùng vars.get("ten") để đọc, vars.put("ten", "giatri") để ghi. Đây là cầu nối giữa Groovy và phần còn lại của test plan.
  • props — properties toàn cục của cả test (JMeterProperties), chia sẻ giữa mọi thread. Dùng cho dữ liệu global như một counter chung.
  • prev — kết quả của Sampler ngay trước (SampleResult). Dùng prev.getResponseDataAsString(), prev.getResponseCode(), prev.getTime()...
  • ctx — context của thread (JMeterContext), truy cập sâu vào trạng thái engine.
  • sampler — chính Sampler sắp/đang chạy, cho phép sửa nó trước khi gửi (trong PreProcessor).
  • log — logger, dùng log.info(...) để ghi ra jmeter.log khi debug.
  • SampleResult — trong JSR223 Sampler, chính là object bạn set kết quả (success, response data...).
  • OUTSystem.out, in ra console.
Nắm chắc vars, prev, log là bạn đã đủ để làm 90% công việc thực tế.

Viết inline hay nạp từ file?

Bạn có hai cách đặt code: gõ trực tiếp vào ô Script, hoặc trỏ tới một file .groovy bên ngoài qua ô "Script file". Với test lớn chạy CI/CD, nạp từ file luôn tốt hơn: dễ version control bằng Git, dễ review, dễ tái sử dụng giữa nhiều test plan, và JMeter cache compile file rất tốt. Inline chỉ nên dùng cho đoạn ngắn 5-10 dòng khi đang thử nghiệm nhanh.

Tình huống thực tế

Tình huống 1 — Ký HMAC-SHA256 cho cổng thanh toán (VNPAY-like)

Một team QA tại một công ty fintech ở TP.HCM (giả định tên "PayFast") cần load test API tạo giao dịch của cổng thanh toán nội bộ, mô phỏng theo cách VNPAY yêu cầu. Đặc thù: mỗi request phải kèm một tham số vnp_SecureHash là chữ ký HMAC-SHA256 tính trên toàn bộ danh sách tham số đã sắp xếp theo alphabet, dùng một secret key. Nếu chữ ký sai, server trả về lỗi 70 và request bị loại — coi như test vô nghĩa.

Các Extractor có sẵn của JMeter không thể tính HMAC. Ban đầu team dùng một BeanShell PreProcessor, và khi đẩy lên 2.000 luồng ảo, họ phát hiện một điều kỳ lạ: server báo chỉ chịu tải được ~400 giao dịch/giây, trong khi log server backend cho thấy CPU server còn nhàn. Điều tra ra, thủ phạm là chính máy load generator — BeanShell parse lại đoạn code HMAC mỗi lần chạy, đốt hết CPU. Sau khi chuyển sang JSR223 PreProcessor + Groovy, dùng thẳng javax.crypto.Mac của Java để tính HMAC, cùng con máy đó đo được throughput thật lên tới ~1.100 giao dịch/giây trước khi server mới thực sự nghẽn.

Bài học: đừng bao giờ để công cụ đo trở thành điểm nghẽn. Khi thấy throughput trần thấp bất thường mà tài nguyên server đích còn dư, hãy nghi ngờ máy tạo tải và script scripting trước tiên. Và luôn ưu tiên Groovy compiled thay vì BeanShell.

Tình huống 2 — Build payload JSON động cho ví điện tử

Một sàn thương mại điện tử (giả định "ShopViet") cần test API nạp tiền vào ví. Mỗi request nạp tiền cần một body JSON trong đó có requestId duy nhất theo định dạng SHOPVIET_yyyyMMddHHmmss_<số ngẫu nhiên>, một amount lấy từ CSV nhưng phải nhân với tỉ giá đọc từ một biến toàn cục, và signature ký trên chuỗi đó. Đây là logic mà không component đơn lẻ nào của JMeter làm được, nhưng một JSR223 PreProcessor 15 dòng Groovy giải quyết gọn: đọc amount từ vars.get("amount"), đọc tỉ giá từ props.get("rate"), tạo timestamp bằng new Date().format(...), ghép chuỗi, ký, rồi vars.put("requestBody", json) để HTTP Sampler dùng ${requestBody} làm body.

Điểm hay: khi cần đổi logic sinh requestId, họ chỉ sửa một file build_payload.groovy trong repo, không phải mở giao diện JMeter kéo-thả lại. CI/CD tự động chạy với file mới.

Bài học: JSR223 biến JMeter từ công cụ gửi request tĩnh thành công cụ mô phỏng client thật. Với hệ thống VN có nhiều tầng bảo mật và định danh giao dịch, đây gần như là kỹ năng bắt buộc.

Tình huống 3 — Assertion nghiệp vụ mà Response Assertion không làm nổi

Một ngân hàng số cần đảm bảo rằng dưới tải cao, API truy vấn số dư không bao giờ trả về số dư âm hoặc số dư lệch quá 0.01 so với giá trị kỳ vọng. Response Assertion thường chỉ so khớp chuỗi/regex, không tính toán số học được. Team dùng JSR223 Assertion: parse JSON response bằng groovy.json.JsonSlurper, lấy trường balance, và nếu balance < 0 thì AssertionResult.setFailure(true) kèm thông báo rõ ràng. Nhờ đó, khi chạy soak test 6 tiếng, họ bắt được một race condition xuất hiện ở phút thứ 214 khiến số dư âm — thứ mà một assertion so-khớp-chuỗi thông thường sẽ bỏ sót hoàn toàn.

Bài học: JSR223 Assertion cho bạn kiểm chứng đúng nghiệp vụ chứ không chỉ đúng cú pháp response. Trong performance testing, một lỗi logic chỉ lộ ra dưới tải mới là lỗi đáng giá nhất.

Hướng dẫn từng bước

Ta sẽ làm một ví dụ hoàn chỉnh: dùng JSR223 PreProcessor để ký HMAC-SHA256 cho một request thanh toán.

Bước 1 — Thêm element. Chuột phải vào HTTP Request Sampler → Add → Pre Processors → JSR223 PreProcessor.

Bước 2 — Chọn ngôn ngữ. Ở ô "Language", chọn groovy (Apache Groovy...). Đảm bảo tick "Cache compiled script if available". Đây là bước quyết định hiệu năng.

Bước 3 — Viết script. Gõ vào ô Script:

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

def secret = props.get("hmac_secret") ?: "sandbox_secret_key" def orderId = "SHOPVIET_" + new Date().format("yyyyMMddHHmmss") + "_" + org.apache.commons.lang3.RandomStringUtils.randomNumeric(6) def amount = vars.get("amount") // đọc từ CSV Data Set

// Chuỗi dữ liệu cần ký, sắp xếp theo alphabet key def data = "amount=${amount}&orderId=${orderId}"

// Tính HMAC-SHA256 def mac = Mac.getInstance("HmacSHA256") mac.init(new SecretKeySpec(secret.bytes, "HmacSHA256")) def sig = mac.doFinal(data.bytes).encodeHex().toString()

// Đẩy ngược ra biến để HTTP Sampler dùng vars.put("orderId", orderId) vars.put("signature", sig)

log.info("Signed order ${orderId} sig=${sig.take(12)}...")

Bước 4 — Dùng biến trong Sampler. Trong HTTP Request, body hoặc tham số dùng ${orderId}, ${amount}, ${signature}. JMeter sẽ thay giá trị đã sinh ở bước trên.

Bước 5 — Chạy thử 1 luồng, bật debug. Thêm một Debug Sampler + View Results Tree để xác nhận orderIdsignature được sinh đúng. Kiểm tra jmeter.log để thấy dòng log.info của bạn.

Bước 6 — Tắt log info trước khi load thật. Đây là điểm dân chuyên nghiệp hay quên. log.info(...) chạy hàng chục nghìn lần/giây sẽ làm nghẽn I/O đĩa. Trước khi chạy tải thật, đổi thành log.debug(...) hoặc xóa hẳn, và luôn chạy ở Non-GUI mode.

Bước 7 — Chuyển script ra file khi ổn định. Copy đoạn Groovy vào sign_payment.groovy, ở JSR223 PreProcessor điền đường dẫn vào ô "Script file", để trống ô Script. Commit file vào Git.

Lỗi thường gặp & mẹo

Lỗi 1 — Vẫn dùng BeanShell theo thói quen. Nhiều test plan cũ đầy BeanShell element. Chỉ cần thay bằng JSR223 + Groovy là throughput đo được của máy tạo tải tăng đáng kể. Hãy soát toàn bộ plan và thay thế trước mỗi lần load test lớn.

Lỗi 2 — Không bật cache compile. Nếu bạn nạp script từ file mà cache không hiệu lực (ví dụ tên file chứa biến động), JMeter compile lại mỗi lần. Giữ đường dẫn file tĩnh và tick "Cache compiled script".

Lỗi 3 — Lạm dụng log.info trong vòng lặp tải. Một câu log tưởng vô hại, nhân với 50.000 lần/giây, biến thành bottleneck I/O. Chỉ log khi debug, và dùng mức debug, không info.

Lỗi 4 — Tạo object nặng bên trong script mỗi lần chạy. Ví dụ khởi tạo lại một ObjectMapper hay compile lại một Regex Pattern trong từng lần thực thi PreProcessor. Với object dùng chung bất biến, hãy cân nhắc lưu vào props một lần rồi tái sử dụng, thay vì new liên tục.

Lỗi 5 — Nhầm giữa varsprops. vars chỉ sống trong một thread; props chia sẻ toàn cục. Nếu bạn vars.put một token rồi mong thread khác đọc được, nó sẽ trống. Dữ liệu global (counter chung, cấu hình) phải đi qua props.

Lỗi 6 — Quên rằng vars.get() luôn trả về String. Muốn tính toán số học phải ép kiểu: vars.get("amount") as Integer hoặc Integer.parseInt(vars.get("amount")). Quên ép kiểu là ra kết quả nối chuỗi sai bét.

Mẹo hiệu năng vàng: nguyên tắc là "script càng ngắn, càng ít cấp phát bộ nhớ, càng tốt". Máy tạo tải phải dành CPU để sinh request, không phải để chạy business logic phức tạp của bạn. Nếu một PreProcessor Groovy tốn 5ms mỗi lần chạy, ở 2.000 luồng nó đã ăn mất một lượng CPU khổng lồ và bóp méo throughput. Luôn đo overhead của chính script bằng cách so throughput có và không có nó ở tải thấp.

Mẹo debug: khi script báo lỗi khó hiểu, mở jmeter.log — Groovy in stack trace đầy đủ ở đó, chi tiết hơn nhiều so với dòng lỗi ngắn trên View Results Tree.

Bài tập thực hành

  • Thay thế BeanShell. Lấy một test plan có sẵn (hoặc tự tạo) dùng BeanShell PreProcessor sinh một timestamp. Chuyển nó sang JSR223 + Groovy. Chạy cùng một kịch bản 500 luồng trong 60 giây ở Non-GUI mode với cả hai phiên bản, ghi lại throughput trung bình của máy tạo tải. Ghi nhận chênh lệch.
  • Sinh payload động. Viết một JSR223 PreProcessor Groovy đọc userId từ CSV Data Set, tạo một requestId duy nhất dạng TEST_<timestamp>_<random 4 số>, ghép thành body JSON và đẩy ra biến ${payload}. Xác nhận bằng Debug Sampler rằng mỗi luồng có requestId khác nhau.
  • Ký HMAC. Dựa trên ví dụ trong bài, viết script tính HMAC-SHA256 cho chuỗi amount=100000&orderId=ABC123 với secret my_secret. So kết quả với một công cụ HMAC online để chắc chắn thuật toán khớp cách server thật kỳ vọng.
  • Assertion nghiệp vụ. Viết một JSR223 Assertion parse JSON response {"balance": ...} bằng JsonSlurper và fail nếu balance âm. Tự giả lập một response âm để xác nhận assertion đánh fail đúng.
  • Đo overhead. Với script ở bài 3, chạy 1.000 luồng trong 2 phút, sau đó tắt PreProcessor và chạy lại. So sánh chênh lệch throughput để ước lượng chi phí CPU mà chính script HMAC gây ra.

Tóm tắt

JSR223 + Groovy là cách chuẩn, hiện đại để viết logic tùy biến bên trong JMeter, thay thế hoàn toàn BeanShell vốn chậm và đã bị khuyến cáo ngừng dùng. Groovy nhanh vì JMeter compile và cache bytecode, còn BeanShell thì thông dịch lại mỗi lần chạy — điều cực kỳ nguy hiểm khi máy tạo tải phải xử lý hàng chục nghìn lần thực thi mỗi giây.

Bạn dùng JSR223 dưới nhiều dạng — PreProcessor để chuẩn bị/ký request, PostProcessor để trích xuất phức tạp, Assertion để kiểm chứng nghiệp vụ, Sampler để thực hiện hành động tùy ý — và luôn có sẵn các object vars, props, prev, log, sampler để tương tác với test plan. Với hệ thống Việt Nam như cổng thanh toán, ví điện tử, ngân hàng số vốn đầy chữ ký HMAC và payload động, đây là kỹ năng gần như bắt buộc để mô phỏng đúng client thật.

Nguyên tắc sống còn: script phải ngắn, nhẹ, bật cache compile, và không được biến máy tạo tải thành bottleneck. Luôn đo overhead của chính script, tắt log info khi chạy tải thật, và đưa script ra file để version control. Làm chủ JSR223 chính là bước chuyển từ "người dùng JMeter" thành "kỹ sư hiệu năng thực thụ".