Python server multi-process stress testing tool
This article describes a multi-process stress testing tool implemented by Python. The features of this tool are as follows:
In most cases, stress testing is generally applicable to IO-intensive scenarios (such as access interfaces and waiting for responses). In this scenario, multi-threaded and multi-process separation is not obvious (see GIL for details ). However, if word table parameter encryption or returned content verification occurs, the multi-process transmission efficiency is improved significantly.
You can specify the QPS for sending pressure. Based on the concurrency and request time, You can estimate the peak QPS for sending. For example, if the concurrency is 10 and the response time is 100 ms, the QPS peak value should be (1 s/100 ms * 10) = 100, this tool can maintain the QPS stably at a volume smaller than the peak value.
Why do we need a DIY stress testing tool? Common server pressure testing tools, such as http_load and jmeter, are either http or code-based. For example, during the stress test of the thrift interface, it is difficult to extend the java program through jmeter. However, when it comes to scenario-based stress testing or strange sdks, for example, the interface for stress testing in this article is the python message SDK automatically generated through java code and involves Scenario-Based stress testing, it is difficult to solve this problem through a general server pressure test tool.
1. Press the code
Decoupling
The following is the implementation of the stress testing code. As you can see, I use the abc Package here to make an abstract class.
The business test code, such as automated case, inherits the abstract class and obtains the stress test capability, decoupling the stress test from the automated test.
There are two abstract methods.
Vocab ()-constructor vocabulary
Press ()-publishing Logic
It is decorated by the @ abc. abstractmethod modifier and must be implemented in the subclass.
The run () method is used for stress testing. After the sub-class vocabulary method and the stress logic are implemented, you can directly call the run () method for stress testing.
Fixed QPS
Fixed QPS is implemented through the management process. There are two processes:
One is the worker_process process, which calls the press () Sending pressure logic function, and this process can specify the concurrency concurrent, which is the actual sending pressure process, it is worth noting that time is used in worker_process. sleep () is used to control the sending speed.
The other is the manager_process process, which calculates the actual qps at intervals, compares it with the set qps, and then adjusts the sleep time in worker_process. For example, the actual qps is less than the set qps, then you need to sleep for a while.
I have to mention how multi-process variables are shared?
Here we use the Manager package in multiprocessing. This package provides the ability of multi-process shared variables. Here I use the Namespace data structure to store multi-process counts. During use, I suspect that the Manager Namespace shares variables between processes by reading and writing files. I have not studied this in depth.
#-*-Coding: UTF-8 -*-
Import abc
Import time
From multiprocessing import Lock, Process, Manager
Class Press (object ):
_ Metaclass _ = abc. ABCMeta
Def _ init _ (self, qps = 100, concurrent = 10 ):
Self. qps = qps
Self. concurrent = concurrent
Self. mutex = Lock ()
Self. local = Manager (). Namespace ()
Self. local. count = 0
Self. local. sleep = 0.1
Self. manager_gap = 0.5
Self. precision = 0.1
Self. vocab_list = list ()
Self. vocab ()
Def manager_process (self ):
While True:
With self. mutex:
Current_qps = self. local. count/self. manager_gap
Self. local. count = 0
Print self. local. sleep, current_qps
If current_qps <self. qps:
Self. local. sleep = self. local. sleep * (1.0-self. precision)
Else:
Self. local. sleep = self. local. sleep * (1.0 + self. precision)
Time. sleep (self. manager_gap)
Def worker_process (self ):
While True:
With self. mutex:
Self. local. count + = 1
Time. sleep (self. local. sleep)
Self. press ()
@ Abc. abstractmethod
Def vocab (self ):
Return
@ Abc. abstractmethod
Def press (self ):
Return
Def run (self ):
Processes = [Process (target = self. worker_process) for index in range (self. concurrent)]
Processes. append (Process (target = self. manager_process ))
For process in processes:
Process. start ()
For process in processes:
Process. join ()
2. Actual Stress Testing
An example of sending pressure is provided. In three steps ~
QueryVmPress inherits the Press class and obtains the stress sending capability.
Then the vocab method is implemented to construct the word table.
The press method is implemented. here we can see that QueryVmScenario. press_vm (vocab) and QueryVmScenario are deployed in an automated case. Only one of the interfaces is called. The compilation of this interface is complicated, and it is also the reason why you need to make a stress testing tool yourself.
#-*-Coding: UTF-8 -*-
Import random
From query. query_vm_scenario import QueryVmScenario
From db. vm_dao import Dao as vm_dao
From db. account_dao import Dao as account_dao
From press import Press
From lib import common
From vocab import Vocab
Class QueryVmVocab (Vocab ):
Def _ init _ (self ):
Vocab. _ init _ (self)
Class QueryVmPress (Press ):
Def _ init _ (self, qps = 100, concurrent = 10 ):
Press. _ init _ (self, qps, concurrent)
Def vocab (self ):
For account in account_dao.query_all_account (limit = 10 ):
Account_name = account [1]
Account_password = account [2]
Res = common. login_by_account (account_name, account_password)
For item in vm_dao.query_vm_by_account (account_name, limit = 100 ):
Vm_uuid = item [1]
Vocab = QueryVmVocab ()
Vocab. add ('session _ uuid ', res. inventory. uuid)
Vocab. add ('vm _ uuid ', vm_uuid)
Self. vocab_list.append (vocab)
Return self. vocab_list
Def press (self ):
Vocab = self. vocab_list [random. randint (0, len (self. vocab_list)-1)]
QueryVmScenario. press_vm (vocab)
If _ name _ = '_ main __':
QueryVmPress (qps = 100, concurrent = 10). run ()
QueryVmPress (qps = 100, concurrent = 10). run (), the pressure test is performed according to qps.
0.1 20.0
0.09 40.0
0.081 60.0
0.0729 80.0
0.06561 60.0
0.059049 80.0
0.0531441 60.0
0.04782969 80.0
0.043046721 80.0
0.0387420489 80.0
0.03486784401 80.0
0.031381059609 100.0
0.0345191655699 80.0
0.0310672490129 88.0
0.0279605241116 92.0
0.0251644717005 100.0
0.0276809188705 80.0
0.0249128269835 100.0
0.0274041096818 100.0
0.03014452065 80.0
0.027130068585 100.0
0.0298430754435 80.0
0.0268587678991 100.0
0.029544644689 92.0
The first column is the sleep time, and the second column is the actual QPS. As you can see, the qps will be dynamically stabilized on the set value.
3. Mixed pressure
This can be done when multiple interfaces are mixed.
First, write the python class for single pressure. In the code for single pressure, you can see that I have implemented the QueryVmVocab class and the table name is the word table type. This class is integrated from Vocab, vocab is a dictionary encapsulation.
During mixed pressure, the word table is summarized and shuffled first. When the word table is popped up, isinstance is used to determine the Word Table type and different stress issuing functions are called for stress testing.
Vocab implementation
#-*-Coding: UTF-8 -*-
Import abc
Class Vocab (object ):
_ Metaclass _ = abc. ABCMeta
Def _ init _ (self ):
Self. vocab = dict ()
Def add (self, key, value ):
Self. vocab [key] = value
Def get (self, key ):
Return self. vocab. get (key)
Def remove (self, key ):
Del self. vocab [key]
Mixed pressure implementation
#-*-Coding: UTF-8 -*-
Import random
From press import Press
From query_eip_press import QueryEipPress, QueryEipVocab
From query_image_press import QueryImagePress, QueryImageVocab
From query_snapshot_press import QuerySnapshotPress, QuerySnapshotVocab
From query_vm_press import QueryVmPress, QueryVmVocab
From query. query_eip_scenario import QueryEipScenario
From query. query_image_scenario import QueryImageScenario
From query. query_snapshot_scenario import QuerySnapshotScenario
From query. query_vm_scenario import QueryVmScenario
Class MixedPress (Press ):
Def _ init _ (self, qps = 100, concurrent = 10 ):
Press. _ init _ (self, qps, concurrent)
Def vocab (self ):
Self. vocab_list.extend (QueryEipPress (). vocab ())
Self. vocab_list.extend (QueryImagePress (). vocab ())
Self. vocab_list.extend (QuerySnapshotPress (). vocab ())
Self. vocab_list.extend (QueryVmPress (). vocab ())
Def press (self ):
Vocab = self. vocab_list [random. randint (0, len (self. vocab_list)-1)]
If isinstance (vocab, QueryEipVocab ):
QueryEipScenario. press_eip (vocab)
Elif isinstance (vocab, QueryImageVocab ):
QueryImageScenario. press_image (vocab)
Elif isinstance (vocab, QuerySnapshotVocab ):
QuerySnapshotScenario. press_snapshot (vocab)
Elif isinstance (vocab, QueryVmVocab ):
QueryVmScenario. press_vm (vocab)
If _ name _ = '_ main __':
MixedPress (200, 50). run ()
Postscript
This is just a small Implementation of the function and is for your reference. If something is wrong, I hope you can correct it.