Some time ago, a project was about to go live, and we needed to pressure test the core interface; since our interface is gRPC protocol, we found that there are not as many pressure testing tools as HTTP.

Finally I found the tool ghz, which is also very full-featured.

Afterwards I wondered why there are so few tools for gRPC piezos, what are the difficulties? In order to verify this problem, I am going to write a tool myself.

Features

It took about a weekend to complete the features before and after.

https://github.com/crossoverJie/ptg/

It is also a command line tool that works as shown above; the full command is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
NAME:
   ptg - Performance testing tool (Go)
USAGE:
   ptg [global options] command [command options] [arguments...]
COMMANDS:
   help, h  Shows a list of commands or help for one command
GLOBAL OPTIONS:
   --thread value, -t value              -t 10 (default: 1 thread)
   --Request value, --proto value        -proto http/grpc (default: http)
   --protocol value, --pf value          -pf /file/order.proto
   --fully-qualified value, --fqn value  -fqn package.Service.Method
   --duration value, -d value            -d 10s (default: Duration of test in seconds, Default 10s)
   --request value, -c value             -c 100 (default: 100)
   --HTTP value, -M value                -m GET (default: GET)
   --bodyPath value, --body value        -body bodyPath.json
   --header value, -H value              HTTP header to add to request, e.g. "-H Content-Type: application/json"
   --target value, --tg value            http://gobyexample.com/grpc:127.0.0.1:5000
   --help, -h                            show help (default: false)

Considering the audience, both HTTP and gRPC interface piezos are supported.

The parameters required to do gRPC piezos are a bit more.

1
ptg -t 10 -c 100 -proto grpc  -pf /xx/xx.proto -fqn hello.Hi.Say -body test.json  -tg "127.0.0.1:5000"

For example, you need to provide the path to the proto file, the specific request parameters, and the full path name of the requested interface.

Currently, only the most common unary call is supported, but you can stream if needed.

It also supports two types of pressure tests: time and number of pressure tests.

Installation

For those who want to experience it, if you have a local go environment, you can run it directly at

1
go get github.com/crossoverJie/ptg

If you don’t have an environment, you can download the version that corresponds to your environment from the release page and unzip it.

https://github.com/crossoverJie/ptg/releases

Design Patterns

There are still several points that I would like to share with you throughout the development process, starting with the design patterns.

Because the design was started with the need to support different pressure test patterns (number of times, time; other patterns can be added later).

So I defined a set of interfaces based on the life cycle of the pressure test.

1
2
3
4
5
6
7
8
9
type (
	Model interface {
		Init()
		Run()
		Finish()
		PrintSate()
		Shutdown()
	}
)

As you can see from the names, they correspond to.

  • Press test initialization
  • Run piezo
  • Stop piezo
  • Print piezo information
  • Closing the program and releasing resources

and then implemented in two different patterns.

This is actually a classic principle of dependency inversion.

Programmers should rely on abstract interfaces for programming and not on concrete implementations.

In fact, this is what we call interface-oriented programming in Java; this programming technique is commonly used in developing frameworks, SDKs, or multiple implementations of a business.

The benefits are of course obvious. When the interface is defined, different businesses only need to implement their own business according to the interface, and it will not affect each other at all; it is easy to maintain and extend.

Support for HTTP and gRPC is also implemented in the same way.

1
2
3
4
5
type (
	Client interface {
		Request() (*Response, error)
	}
)

Of course, the prerequisite is that the interface definition needs to be well thought out in the early stages and cannot be modified frequently afterwards, so that the interface is meaningless.

goroutine

Another thing is that I have to say that the goroutine+select+channel concurrent programming model works really well and is very easy to understand.

It’s easy to write a concurrent code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func (c *CountModel) Init() {
	c.wait.Add(c.count)
	c.workCh = make(chan *Job, c.count)
	for i := 0; i < c.count; i++ {
		go func() {
			c.workCh <- &Job{
				thread:   thread,
				duration: duration,
				count:    c.count,
				target:   target,
			}
		}()
	}
}

For example, if you need to initialize N goroutines to execute a task, just use the go keyword and then use the channel to write the task.

Of course, when using goroutine+channel together, you have to be careful about goroutine leaks; simply put, there are still goroutines left when the programmer exits.

A common example is writing data to an unbuffered channel. When there is no other goroutine to read the data, the writing goroutine will keep blocking, leading to a leak.

Summary

If you have gRPC interface pressure testing needs, you are welcome to try it and give your valuable opinions; of course, HTTP interface is also available.

Source code address:

https://github.com/crossoverJie/ptg/