Skip to content

Commit

Permalink
Add an alternative locust client (#46)
Browse files Browse the repository at this point in the history
* add locust client as an alternative

* add a feature: retry if FAIL_RATIO's high

* provide both httpclient and grpcclient

* Add README.md of the locust client
  • Loading branch information
YukioZzz authored and ZHANGWENTAI committed Oct 31, 2024
1 parent f175286 commit f316c3f
Show file tree
Hide file tree
Showing 17 changed files with 1,427 additions and 6 deletions.
70 changes: 65 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/alibaba/morphling

go 1.16
go 1.22

require (
github.com/ghodss/yaml v1.0.0
Expand All @@ -12,13 +12,9 @@ require (
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.3
github.com/jinzhu/gorm v1.9.16
github.com/jinzhu/now v1.1.2 // indirect
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
github.com/onsi/gomega v1.8.1
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 // indirect
google.golang.org/genproto v0.0.0-20201015140912-32ed001d685c // indirect
google.golang.org/grpc v1.32.0
google.golang.org/protobuf v1.25.0
k8s.io/api v0.18.5
Expand All @@ -29,6 +25,70 @@ require (
sigs.k8s.io/controller-runtime v0.6.0
)

require (
cloud.google.com/go v0.38.0 // indirect
github.com/beorn7/perks v1.0.0 // indirect
github.com/blang/semver v3.5.0+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/zapr v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/google/go-cmp v0.5.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/googleapis/gnostic v0.3.1 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.1.3 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.4.1 // indirect
github.com/prometheus/procfs v0.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 // indirect
golang.org/x/text v0.3.3 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
gomodules.xyz/jsonpatch/v2 v2.0.1 // indirect
google.golang.org/appengine v1.5.0 // indirect
google.golang.org/genproto v0.0.0-20201015140912-32ed001d685c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
k8s.io/apiextensions-apiserver v0.18.2 // indirect
k8s.io/apiserver v0.18.5 // indirect
k8s.io/component-base v0.18.5 // indirect
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 // indirect
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 // indirect
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

replace (
k8s.io/api => k8s.io/api v0.18.5
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.18.5
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,6 @@ github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLY
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
Expand Down
27 changes: 27 additions & 0 deletions pkg/client_locust/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM python:3.6

RUN if [ "$(uname -m)" = "ppc64le" ] || [ "$(uname -m)" = "aarch64" ]; then \
apt-get -y update && \
apt-get -y install gfortran libopenblas-dev liblapack-dev && \
pip install cython 'numpy>=1.13.3'; \
fi
RUN GRPC_HEALTH_PROBE_VERSION=v0.3.1 && \
if [ "$(uname -m)" = "ppc64le" ]; then \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-ppc64le; \
elif [ "$(uname -m)" = "aarch64" ]; then \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-arm64; \
else \
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64; \
fi && \
chmod +x /bin/grpc_health_probe

WORKDIR /workspace
ADD requirements.txt requirements.txt
RUN /usr/local/bin/python -m pip install --upgrade pip
#RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

ADD . /workspace

ENV BATCH_SIZE 1
ENTRYPOINT ["/bin/sh", "-c", "sleep 100000"]
41 changes: 41 additions & 0 deletions pkg/client_locust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Motivation
As we might want the client to perform a more customized load test to the inference server in a more simple and flexible way, an idea is to use locust as the backend so that the user can directly specify load test params or provide its own locusttest script. A demo client image based on locust is provided here.

## File structure description
Three meta files describe how to build and use the image.

- `README.md`
- `Dockerfile`: used to build the client image
- `jobtemplate.yaml`: a trial client job template using `morphling-client-plugin` image

The other files will be used in the client image.

- `requirements.txt`: python scripts dependencies
- `morphling_client_locust.py`: main function, responsible for args/env parsing, locust invocation and result saving. When the error rate exceeds `FAIL_RATE`, it will retry with a lower `LOCUST_NUM_USERS`
- `invokust`: locust wrapper,responsible for launching the pressure test and returning the results. As locust itself does not provide a complete library API, and is more often called as a CLI tool, a wrapper is needed here. In addition, a new feature which supports prometheus metrics export is added.
- `locust_grpc.py`: constructed a `GrpcUser` super class, easy to be inherited and extended by the user. It will automatically record the results via the event hook `events.request.fire`. An error threshold exit mechanism is also enabled here by the event listener.
- `locustfile_grpcuser.py` or `locustfile_httpuser.py`: the load test demo script, reserved models are provided which can be used directedly; when cusomization is needed, what we need to do is just to inherit class `GrpcUser` or the native `HttpUser`, and define the function decorated by `@task`
- `api_pb2.py` and `api_pb2_grpc.py`: used for database connection
- `image.jpg`: used for the demo load test

As described above, the client's functions are decoupled into several modules/files as a plug-in approach. The user can just specify the param or provids its own locustfile without effort. gRPC and HTTP protocols are both supported.

## Usage
Currently, we can specify test parameters via ENV.

The exposed APIs are as follows, see examples in the `jobtemplate.yaml`:

- `LOCUST_NUM_USERS`: maximum number of concurrent users, default: 10
- `LOCUST_SPAWN_RATE`: spawning rate of concurrent users, default: 10
- `LOCUST_RUN_TIME`: test running duration, default: 15
- `LOCUST_LOCUSTFILE`: custom test file name, default: locustfile.py
- `LOCUST_METRICS_EXPORT`: export Prometheus metrics or not, default: False
- `FAIL_RATIO`: fail ratio threshold, default: 0.2
- `PRINTLOG`: print log or not, default: false

## Conclusion / Pro and Cons
To avoid reinvent the wheels, the open-source locust load testing tool is chosen here because of its ease of use and good scalability. It supports HTTP/gRPC and many other protocols and at the same time it can be extended easily to perform distributed load testing. Therefore, compared with the heavyweight Jmeter and lightweight K6, which is hard to implement complex requests, locust is more suitable.
But there are also some drawbacks.

- locust itself does not provide a complete Library API and is mostly called as a CLI tool. Therefore, the invokust locust wrapper is used here, which is responsible for launching the load test and returning the results. However, only local runner mode is currently supported. The distributed load tests are not supported now. Since in most cases the QPS is less than 1000 for DL inference tasks on CPU, the pressure generated by the local runner is sufficient.
- QPS results with SLO guarantee is currently not supported yet. Most of the pressure testing tools do not have this option. It might be realized in the future by the same event hook mechanism as `FAIL_RATIO` retry strategy or by filtering the final qps result.
Loading

0 comments on commit f316c3f

Please sign in to comment.