Nội dung bài viết
I. Thread là gì?
- Program (1 chương trình) là tập hợp của các câu lệnh dưới dạng text, có thể viết bằng nhiều ngôn ngữ khác nhau: java, python, C++…
- Khi 1 program được chạy trên OS (Operating System – hệ điều hành) thì nó gọi là process. Mỗi process này sẽ được OS cấp phát memory (bộ nhớ) khác nhau, để process A ko thể access vào phần memory của process B. Ngoài ra, 1 program có thể chạy nhiều process, ví dụ bạn mở Chrome lên thì nó chạy khá nhiều process (tầm 10 process hoặc hơn). Mỗi process sẽ có 1 cái gọi là PID (Process ID) để phân biệt các process với nhau, nhiều khi bạn muốn tắt 1 chương trình nào đó, bạn có thể bảo OS kill process đó đi bằng PID.
Vì nhu cầu Multitasking (đa nhiệm), OS phải chia thời gian chạy cho mỗi process, gọi là time-slicing
vào khoảng 15-20ms. Việc chuyển running từ process này sang process khác gọi là context switch. Và vì nó chuyển qua lại nhanh quá, nên người dùng có cảm giác như là máy tính đang thực sự làm được nhiều việc 1 lúc (vừa nghe nhạc, vừa lướt fb). Chốt lại, ở 1 thời điểm thì 1 CPU chỉ run được 1 process thôi.
Nhưng máy tính ngày càng phát triển, laptop hiện tại đã có rất nhiều core và có thể thực sự run được nhiều process cùng lúc, đúng nghĩa. Ví dụ máy tính của mình 4 core vật lý, mỗi core có 2 processors -> tổng có 8 logical processors, có nghĩa là máy tính làm được 8 việc cùng lúc trong 1 thời điểm.
Tuy nhiên, 1 process không chỉ làm 1 việc duy nhất, nó có thể làm nhiều việc và nó có nhu cầu làm nhiều việc đó song song. Giả sử process A phải làm 6 việc:
Instruction-1 Instruction-2 Instruction-3 Instruction-4 Instruction-5 Instruction-6
Trong đó, 1-2-3 là phải run theo thứ tự, 4-5-6 cũng phải run theo thứ tự, nhưng 1-2-3 và 4-5-6 có thể run độc lập với nhau. Developer có 2 phương án dưới đây:
Nhìn thế này bạn cũng có thể thấy cách 2 tối ưu về performance hơn, nó chạy song song 2 tập hợp 1-2-3 và 4-5-6. Việc run các tập hợp (sequence of execution) trong 1 process gọi là Thread. Trong 1 process sẽ có 1 hoặc nhiều thread đang chạy. Các thread này sẽ share nhau phần memory mà OS cấp cho process. Tuy nhiên nó sẽ khác nhau: program counter
và stack
.
Program counter
được dùng để CPU biết rằng đang run cái thread này đến đâu. Ví dụ: CPU runthread 1
được 1 nửa thì hết 20ms, nó phải đổi sang run thread khác, nó phải save cái thông tin là nó đang runthread 1
đến đâu rồi, để lát còn run tiếp, cứ như vậy cho đến khi thread run xong hoàn toàn.Stack
là để thread lưu value của các biến mà các Thread tạo ra và cần dùng trong quá trình run.
Chốt lại, nó sẽ như sau:
Important: ở các hệ điều hành hiện tại, thread mới là cái được setup để run trên CPU, do đó context switch
ở phía trên sẽ là dành cho thread. Một program mà sử dụng multiple threads thì gọi là multi-threaded program.
II. Thread trong JMeter
JMeter là 1 java program và bất kỳ 1 java program nào cũng là multi-threaded, nó có vài thread chạy ngầm mà mình không biết, ví dụ garbage collector (chuyên đi dọn rác để lấy lại memory)
Java sẽ làm 2 steps để run được 1 thread:
- Khởi tạo Thread: Java sẽ cấp memory ở heap size cho Thread đấy. Tổng heap size default của JMeter là 1Gb, nên nếu bạn setting để chạy nhiều thread vượt quá con số 1Gb đó, sẽ bị lỗi out of memory, và bạn sẽ increase cái heap size đó lên rồi mới chạy lại được. Tuy nhiên, cái đó cũng chưa đủ, nếu bạn khởi tạo quá nhiều thread, nó sẽ vượt ngưỡng có thể run của CPU và sẽ có thể bị lỗi tiếp.
- Run Thread: Java chỉ có thể thông báo cho OS biết rằng nó có 1 thread cần được run, thread này sẽ được schedule để run trong tương lai, chứ Java không thể setting được chính xác khi nào thread này sẽ chạy. Sau khi run xong, thread sẽ chết và sau đó sẽ bị thằng garbage collector ở trên mang đi “chôn”. :)))))
Cái flow trên cũng chính là flow của JMeter khi nó khởi tạo Thread và run các thread mà bạn setting.
Ví dụ cụ thể loại siêu đơn giản:
- Threads=10
- Ramp-up=5
- Loop=1
- Như đã nói ở bài Thread Group, thì ramp-up là số giây để JMeter tạo đủ số threads, có nghĩa là 5s tạo đủ 10 threads, 1s tạo 2 threads. Nói cách khác nó chính là delay việc khởi tạo và run threads.
- Thread số 1 sẽ được khởi tạo và run ngay lập tức (tại giây số 0), nhắc lại, run ở đây là việc JMeter báo cho OS biết có 1 Thread mới và OS sẽ cắt thời gian để run thread này trong tương lai. JMeter ko biết chính xác khi nào Thread được run.
- Sau đó cứ mỗi 0.5s (ramp-up / threads) thì sẽ có 1 threads được tạo và run. Vì Thread 1 được tạo từ giây số 0 nên Thread 10 sẽ được tạo ở giây 4.5.
Đây là đoạn code của JMeter, nó thể hiện đúng cái ý nghĩa của Ramp-up.
final JMeterVariables variables = JMeterContextService.getContext().getVariables(); long lastThreadStartInMillis = 0; int delayForNextThreadInMillis = 0; final int perThreadDelayInMillis = Math.round((float) rampUpPeriodInSeconds * 1000 / numThreads); for (int threadNum = 0; running && threadNum < numThreads; threadNum++) { long nowInMillis = System.currentTimeMillis(); if(threadNum > 0) { long timeElapsedToStartLastThread = nowInMillis - lastThreadStartInMillis; // Note: `int += long` assignment hides lossy cast to int delayForNextThreadInMillis = (int) (delayForNextThreadInMillis + (perThreadDelayInMillis - timeElapsedToStartLastThread)); } ... lastThreadStartInMillis = nowInMillis; startNewThread(notifier, threadGroupTree, engine, threadNum, variables, nowInMillis, Math.max(0, delayForNextThreadInMillis));
Câu hỏi đặt ra là nếu tôi set ramp-up=0 thì sao? (Đây là câu hỏi mà chính mình cũng đã hiểu sai và trả lời sai ở trong bài viết load test)
Thì chỉ đơn giản là ko có thời gian delay giữa các “khởi tạo & run” các thread. Thread 1 được khởi tạo và run, rồi Thread 2 khởi tạo và run … cho đến Thread cuối cùng. Như vậy không có nghĩa là tại giây số 0, tất cả các thread được khởi tạo và run cùng lúc, mà chỉ là giữa các thread sẽ không có thời gian delay.
Áp dụng vào công thức ở phần code phía trên.
final int perThreadDelayInMillis = Math.round((float) rampUpPeriodInSeconds * 1000 / numThreads);
rampUpPeriodInSeconds = 0 --> Math.round((float) rampUpPeriodInSeconds * 1000 / numThreads) = 0 --> perThreadDelayInMillis = 0
Giả sử Thread 1 khởi tạo mất 5ms và gửi lệnh run hết 5ms, thì sau 10ms, Thread 2 bắt đầu được khởi tạo và gửi lệnh run. Cứ lần lượt như vậy cho đến hết.
Câu hỏi khác: Active Threads Overtime là chỉ số gì? JMeter đang đẩy bao nhiêu request /s ?
Active Threads Overtime
là biểu đồ thể hiện các threads đang running ở mỗi 1 thời điểm. Trong hình trên đoạn nằm ngang thể hiện rằng có 30 threads đang running trong khoảng thời gian đó. Một lần nữa, bạn phải nhớ rằng nó đang ở trạng thái Active, chứ nó ko thực sự run, do nó phụ thuộc vào CPU, cứ sau mỗi lần time-slice thì CPU sẽ run thread khác. Tuy nhiên, để cho đơn giản thì mình cứ hiểu là đang có 30 threads trạng thái active – đang run.
Hình trên là mô phỏng công việc của 1 Thread khi send 1 request. Các công đoạn trên có thể chia thành 3 giai đoạn:
- JMeter time: lúc JMeter run, không liên quan gì đến server, gồm có Prepare request và Handle response.
- Server time: Nhận được thông tin request và process để trả lời, chỉ run ở Server.
- Network time: thời gian chuyển thông tin qua đường truyền, JMeter và Server đều ngồi chơi.
Vì vậy, nếu nói chính xác ra thì 30 active thread kia, ở 1 thời điểm nhất định, sẽ có thread rơi vào JMeter time, hoặc Server time hoặc Network Time. Nói 30 active thread đang running thì đúng, nhưng nói 30 thread đang send request thì sai. Việc hiểu tường tận sẽ giúp bạn tự tin và giải thích được 1 vài tình huống, tuy nhiên ko nhất thiết phải chi tiết như vậy khi nói chuyện với khách hàng hay team của bạn.
Ở Closed Model, chỉ khi nào 1 Virtual User (VU) hoàn thành (đợi nhận hết toàn bộ response) thì 1 VU khác mới được tạo ra và run. Khi response time ngắn, JMeter sẽ gửi được nhiều request lên hơn và ngược lại khi response time dài, JMeter sẽ gửi được ít request lên. Vì vậy, ta có thể tạm coi rate để send request chính là throughput luôn. Vì 2 thằng nó tỉ lệ thuận với nhau, server nhanh thì JMeter cũng sẽ nhanh, server chậm thì JMeter cũng sẽ chậm.
Tuy nhiên, ở Open Model thì nó sẽ không quan tâm là thằng VU trước có hoàn thành xong hay ko, JMeter sẽ liên tục đẩy tải vào để đạt đủ target mà nó setting từ trước. Do đó rate send request sẽ là con số mà mình setting, còn throughput vẫn được tính bằng (tổng request) / (tổng time).
Ví dụ: rate đẩy load của JMeter sẽ là 180 request/min = 3 req/s.
Nếu bạn muốn biết về Closed Model, Open Model hay các kiến thức khác về performance thì hãy đăng ký khóa học Performance Test nhé. 😀
III. Tổng kết
Đây là 1 bài dài và khá khó, mình đã rất cố gắng giải thích từng thứ một, hi vọng là có ích cho bạn. Happy learning! Còn mình thì đi ngủ đây, đọc code JMeter mệt quá.
Dạ vậy cho em hỏi trường hợp như này:
Em run 1 request giống nhau nếu em config theo 2 kiểu thì liệu nó có cùng cách thức run của JMeter không ạ:
Giả sử run 100 request,
Mong được anh giải thích ạ
“liệu nó có cùng cách thức run của JMeter không” –> câu hỏi không rõ ràng lắm. Nếu em hỏi JMeter sẽ làm thế nào cho mỗi TH trên thì:
– Cách 1: JMeter chỉ tạo ra 1 thread duy nhất, nó sẽ run lần lượt 100 requests từ trên xuống dưới.
– Cách 2: Mỗi Thread Group tạo ra 1 thread và mỗi thread này sẽ run 1 request và config Thread Group run tuần tự thì sẽ có 100 threads run tuần tự, mỗi thread run 1 request.
Cách 1 tiết kiệm hơn 1 chút vì nó ko mất thời gian và tốn resource tạo nhiều threads.
Cả 2 cách mang lại hiệu quả là giống nhau –> run 100 request tuần tự.