Compare commits

...

10 Commits

Author SHA1 Message Date
Saikrishna Edupuganti 0990006efb Provide dpdk-stable 3 LTS and 1 recent release
Testpmd manifests of the last 3 LTS and 1 latest release from stable
repo to help test actual DPDK app instead of sleep.

Currently 17.11 and 19.11 are the only functioning ones without
privileged.

```
NAME              READY   STATUS    RESTARTS   AGE
dpdk-1711         1/1     Running   0          3m5s
dpdk-1811         0/1     Error     0          3m5s
dpdk-1911         1/1     Running   0          3m5s
dpdk-2002         0/1     Error     0          3m5s
```

```
EAL: PCI device 0000:07:06.4 on NUMA socket 0
EAL:   probe driver: 8086:154c net_i40e_vf
EAL: Getting a vfio_dev_fd for 0000:07:06.4 failed
EAL: Requested device 0000:07:06.4 cannot be used
…
testpmd: No probed ethernet devices
EAL: Error - exiting with code: 1
  Cause: Invalid port 1
```

Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
2020-06-03 23:05:17 -07:00
Saikrishna Edupuganti 735f3c5b21 Update multi-net components to latest releases
Multus CNI 3.4.2
SR-IOV CNI 2.3
SR-IOV DP  3.2

Tested as per the README. Works fine.

Signed-off-by: Saikrishna Edupuganti <saikrishna.edupuganti@intel.com>
2020-06-03 21:24:15 -07:00
Miguel Bernal Marin 9b4f0a8582 setup_system: use local admin path for proxy.conf (#328)
Currently the system.conf.d/proxy.conf file is saved at /usr/lib
which is the vendor path, and can be dropped by
"swupd repair --picky --force".

This commit creates the local administrator /etc/systemd/system.conf.d
directory and store the proxy.conf inside.
2020-05-26 10:45:25 -07:00
Antti Kervinen 696861ce66 metrics: change collectd output to host /opt/collectd/run
Currently we loose collectd data from a node when scaling ends to a
system failure on the node - yet this data can be very helpful in root
causing the failure. This patch changes collectd configuration so that
the output will be continuously written to host filesystem instead of
the collectd container overlay that will be lost unless scaling
reaches graceful exit.

Signed-off-by: Antti Kervinen <antti.kervinen@intel.com>
2020-05-19 19:56:54 +01:00
Graham Whaley 07fd8412da metrics: report: Error more cleanly
Clean up the rest of the report R files to allow them to quit
cleanly when they find an error or missing data, so that the
final PDF report gives meaninful errors such as 'No data found',
rather than cryptic R errors.

Signed-off-by: Graham Whaley <graham.whaley@intel.com>
2020-05-19 19:53:23 +01:00
Graham Whaley 058e1753ae metrics: report: quit cleanly on tidy_scaling failure
When there are no files to process, we tend to quit with a loud
and not helpful error. Improve that by spotting the obvious error
cases (such as no files to process for a specific test), and quit
with a nicer error/warning message that ends up in the rendered
report.

Start with the tidy_scaling test. The only clean way to quit a
fragment of Rmarkdown R looks to be to place it inside a function
so we can 'return'. Otherwise, all other forms of 'quit', quit the
whole Rmarkdown render pipeline, which is not what we want - we
want to carry on and try to process the rest of the fragments for
the rest of the tests.

Signed-off-by: Graham Whaley <graham.whaley@intel.com>
2020-05-19 19:53:23 +01:00
Graham Whaley 091e76c3d8 metrics: k8s_scale_net: whitespace fixes
Fix some indentation that had gone rogue.
Note, there are other whitespace fixes that can be done in this file,
it appears to have a mix of tabs and spaces.

Signed-off-by: Graham Whaley <graham.whaley@intel.com>
2020-05-15 09:11:37 -06:00
Graham Whaley 7121418dd3 metrics: Imrove documentation
Improve and expand the documents across the metrics subsystem.
Clarify and re-order some documents. Add some more details around
each individual test.
Note that only the 'rapid' test is currently actively used, and the
other tests may need some nurturing if they are found to be useful.

Signed-off-by: Graham Whaley <graham.whaley@intel.com>
2020-05-15 09:11:37 -06:00
Gabriela Cervantes e10260e99c metrics: Use a specific version of rocker/tidyverse
This PR uses a specific version of rocker/tidyverse as the latest version
does not have the latex-xcolor package which makes impossible to create
the metrics report.

Signed-off-by: Gabriela Cervantes <gabriela.cervantes.tellez@intel.com>
2020-05-13 15:18:05 +01:00
CraigSterrett e732cc693b Updated metrics github location (#323)
The metrics-server package has moved out of the kubernetes incubator
github location and is now in the sigs location.

Signed-off-by: Craig Sterrett <craig.Sterrett@intel.com>
2020-05-06 13:00:07 -07:00
22 changed files with 1701 additions and 1407 deletions
+23 -15
View File
@@ -1,20 +1,28 @@
# Build multus plugin # Build multus plugin
FROM golang:1.10 AS multus FROM busybox AS multus
RUN git clone -q --depth 1 https://github.com/intel/multus-cni.git /go/src/github.com/intel/multus-cni ARG MULTUS_VER=3.4.2
WORKDIR /go/src/github.com/intel/multus-cni RUN wget -O multus.tgz https://github.com/intel/multus-cni/releases/download/v${MULTUS_VER}/multus-cni_${MULTUS_VER}_linux_amd64.tar.gz
RUN ./build RUN tar xvzf multus.tgz --strip-components=1 -C /bin
# Build sriov plugin # Build sriov plugin
FROM golang:1.10 AS sriov-cni FROM golang AS sriov-cni
RUN git clone -q -b dev/k8s-deviceid-model https://github.com/Intel-Corp/sriov-cni.git /go/src/github.com/intel-corp/sriov-cni ARG SRIOV_CNI_VER=2.3
WORKDIR /go/src/github.com/intel-corp/sriov-cni RUN wget -qO sriov-cni.tgz https://github.com/intel/sriov-cni/archive/v${SRIOV_CNI_VER}.tar.gz
RUN ./build RUN mkdir -p sriov-cni && \
tar xzf sriov-cni.tgz --strip-components=1 -C sriov-cni && \
cd sriov-cni && \
make && \
cp build/sriov /bin
# Build sriov device plugin # Build sriov device plugin
FROM golang:1.10 AS sriov-dp FROM golang AS sriov-dp
RUN git clone -q https://github.com/intel/sriov-network-device-plugin.git /go/src/github.com/intel/sriov-network-device-plugin ARG SRIOV_DP_VER=3.2
WORKDIR /go/src/github.com/intel/sriov-network-device-plugin RUN wget -qO sriov-dp.tgz https://github.com/intel/sriov-network-device-plugin/archive/v${SRIOV_DP_VER}.tar.gz
RUN make RUN mkdir -p sriov-dp && \
tar xzf sriov-dp.tgz --strip-components=1 -C sriov-dp && \
cd sriov-dp && \
make && \
cp build/sriovdp /bin
# Build vfioveth plugin # Build vfioveth plugin
FROM busybox as vfioveth FROM busybox as vfioveth
@@ -25,9 +33,9 @@ RUN chmod +x /bin/vfioveth /bin/jq
# Final image # Final image
FROM centos/systemd FROM centos/systemd
WORKDIR /tmp/cni/bin WORKDIR /tmp/cni/bin
COPY --from=multus /go/src/github.com/intel/multus-cni/bin/multus . COPY --from=multus /bin/multus-cni .
COPY --from=sriov-cni /go/src/github.com/intel-corp/sriov-cni/bin/sriov . COPY --from=sriov-cni /bin/sriov .
COPY --from=vfioveth /bin/vfioveth . COPY --from=vfioveth /bin/vfioveth .
COPY --from=vfioveth /bin/jq . COPY --from=vfioveth /bin/jq .
WORKDIR /usr/bin WORKDIR /usr/bin
COPY --from=sriov-dp /go/src/github.com/intel/sriov-network-device-plugin/build/sriovdp . COPY --from=sriov-dp /bin/sriovdp .
+4 -5
View File
@@ -9,11 +9,10 @@ directories on the host with the necessary binaries and configuration files.
### Customization ### Customization
The device plugin will register the SR-IOV enabled devices on the host, specified as The device plugin will register the SR-IOV enabled devices on the host, specified with
`rootDevices` in [sriov-conf.yaml](sriov-conf.yaml). Helper [systemd unit](systemd/sriov.service) `selectors` in [sriov-conf.yaml](sriov-conf.yaml). Helper [systemd unit](systemd/sriov.service)
file is provided, which enables SR-IOV for the above `rootDevices` file is provided, which enables SR-IOV for the above devices. More config options
are listed [here](https://github.com/intel/sriov-network-device-plugin#configurations).
> NOTE: This assumes homogenous nodes in the cluster
### Pre-req (SR-IOV only) ### Pre-req (SR-IOV only)
@@ -2,25 +2,34 @@
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: network-attachment-definitions.k8s.cni.cncf.io name: network-attachment-definitions.k8s.cni.cncf.io
spec: spec:
# group name to use for REST API: /apis/<group>/<version>
group: k8s.cni.cncf.io group: k8s.cni.cncf.io
# version name to use for REST API: /apis/<group>/<version>
version: v1
# either Namespaced or Cluster
scope: Namespaced scope: Namespaced
names: names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: network-attachment-definitions plural: network-attachment-definitions
# singular name to be used as an alias on the CLI and for display
singular: network-attachment-definition singular: network-attachment-definition
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: NetworkAttachmentDefinition kind: NetworkAttachmentDefinition
# shortNames allow shorter string to match your resource on the CLI
shortNames: shortNames:
- net-attach-def - net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
description: 'NetworkAttachmentDefinition is a CRD schema specified by the Network Plumbing
Working Group to express the intent for attaching pods to one or more logical or physical
networks. More information available at: https://github.com/k8snetworkplumbingwg/multi-net-spec'
type: object
properties:
spec:
description: 'NetworkAttachmentDefinition spec defines the desired state of a network attachment'
type: object
properties:
config:
description: 'NetworkAttachmentDefinition config is a JSON-formatted CNI configuration'
type: string
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
@@ -40,17 +49,43 @@ type: kubernetes.io/service-account-token
kind: ClusterRole kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
metadata: metadata:
name: multus-pod-networks-lister name: multus-pod-updater
rules: rules:
- apiGroups: [""] - apiGroups: ["k8s.cni.cncf.io"]
resources: ["pods"] resources:
verbs: ["get"] - '*'
- apiGroups: [""] verbs:
resources: ["pods/status"] - '*'
verbs: ["update"] - apiGroups:
- apiGroups: ["k8s.cni.cncf.io"] - ""
resources: ["*"] resources:
verbs: ["get"] - pods
- pods/status
verbs:
- get
- update
- apiGroups:
- ""
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus-rb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus-pod-updater
subjects:
- kind: ServiceAccount
name: multus-sa
namespace: kube-system
--- ---
kind: ConfigMap kind: ConfigMap
apiVersion: v1 apiVersion: v1
@@ -94,8 +129,9 @@ data:
MASTER_PLUGIN_JSON="$(cat /host/etc/cni/net.d/$MASTER_PLUGIN)" MASTER_PLUGIN_JSON="$(cat /host/etc/cni/net.d/$MASTER_PLUGIN)"
cat > /host/etc/cni/net.d/00-multus.conf <<EOF cat > /host/etc/cni/net.d/00-multus.conf <<EOF
{ {
"cniVersion": "0.3.1",
"name": "multus-cni-network", "name": "multus-cni-network",
"type": "multus", "type": "multus-cni",
"logFile": "/var/log/multus.log", "logFile": "/var/log/multus.log",
"logLevel": "debug", "logLevel": "debug",
"kubeconfig": "/etc/cni/net.d/multus-kubeconfig", "kubeconfig": "/etc/cni/net.d/multus-kubeconfig",
@@ -105,19 +141,6 @@ data:
} }
EOF EOF
--- ---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus-rb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus-pod-networks-lister
subjects:
- kind: ServiceAccount
name: multus-sa
namespace: kube-system
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: DaemonSet kind: DaemonSet
metadata: metadata:
@@ -134,14 +157,14 @@ spec:
spec: spec:
initContainers: initContainers:
- name: multus - name: multus
image: krsna1729/multus-sriov:k8s-1.13 image: krsna1729/multus-sriov:3.4.2-2.3-3.2
command: [ "bash", "-c" ] command: [ "bash", "-c" ]
args: args:
- cp /tmp/cni/bin/{multus,sriov,vfioveth,jq} /host/opt/cni/bin/; - cp /tmp/cni/bin/{multus-cni,sriov,vfioveth,jq} /host/opt/cni/bin/;
/tmp/multus/install-multus-conf.sh; /tmp/multus/install-multus-conf.sh;
/tmp/multus/install-certs.sh; /tmp/multus/install-certs.sh;
echo "Restarting crio kubelet"; echo "Restarting crio kubelet";
systemctl restart crio; # Needed when crio manages ns lifecycle systemctl restart crio;
systemctl restart kubelet; systemctl restart kubelet;
volumeMounts: volumeMounts:
- name: usr-bin - name: usr-bin
@@ -160,7 +183,7 @@ spec:
mountPath: /run/systemd mountPath: /run/systemd
containers: containers:
- name: sriovdp - name: sriovdp
image: krsna1729/multus-sriov:k8s-1.13 image: krsna1729/multus-sriov:3.4.2-2.3-3.2
command: [ "sh", "-c" ] command: [ "sh", "-c" ]
args: args:
- /usr/bin/sriovdp --logtostderr -v 10; - /usr/bin/sriovdp --logtostderr -v 10;
@@ -209,4 +232,3 @@ spec:
path: /var/lib/kubelet/device-plugins/ path: /var/lib/kubelet/device-plugins/
hostNetwork: true hostNetwork: true
hostPID: true hostPID: true
@@ -11,15 +11,15 @@ data:
[ [
{ {
"resourceName": "sriov_netdevice", "resourceName": "sriov_netdevice",
"rootDevices": ["07:00.0"], "selectors": {
"sriovMode": true, "drivers": ["i40evf", "iavf"]
"deviceType": "netdevice" }
}, },
{ {
"resourceName": "sriov_vfio", "resourceName": "sriov_vfio",
"rootDevices": ["07:00.1"], "selectors": {
"sriovMode": true, "drivers": ["vfio-pci"]
"deviceType": "vfio" }
} }
] ]
} }
@@ -0,0 +1,100 @@
---
apiVersion: v1
kind: Pod
metadata:
name: dpdk-1711
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-dpdk
spec:
restartPolicy: Never
containers:
- name: dpdk
image: krsna1729/dpdk:17.11
stdin: true
tty: true
command: [ "/bin/bash", "-c"]
args:
- ls -l /dev/vfio;
testpmd --no-huge -m 2048 -- --stats-period=10 --nb-port=1 --port-topology=chained --auto-start --total-num-mbufs=2048 --forward-mode=macswap;
securityContext:
capabilities:
add:
- IPC_LOCK
resources:
limits:
intel.com/sriov_vfio: '1'
---
apiVersion: v1
kind: Pod
metadata:
name: dpdk-1811
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-dpdk
spec:
restartPolicy: Never
containers:
- name: dpdk
image: krsna1729/dpdk:18.11
stdin: true
tty: true
command: [ "/bin/bash", "-c"]
args:
- ls -l /dev/vfio;
testpmd --no-huge -m 2048 -- --stats-period=10 --nb-port=1 --port-topology=chained --auto-start --total-num-mbufs=2048 --forward-mode=macswap;
securityContext:
capabilities:
add:
- IPC_LOCK
resources:
limits:
intel.com/sriov_vfio: '1'
---
apiVersion: v1
kind: Pod
metadata:
name: dpdk-1911
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-dpdk
spec:
restartPolicy: Never
containers:
- name: dpdk
image: krsna1729/dpdk:19.11
stdin: true
tty: true
command: [ "/bin/bash", "-c"]
args:
- ls -l /dev/vfio;
testpmd --no-huge -m 2048 -- --stats-period=10 --nb-port=1 --port-topology=chained --auto-start --total-num-mbufs=2048 --forward-mode=macswap;
securityContext:
capabilities:
add:
- IPC_LOCK
resources:
limits:
intel.com/sriov_vfio: '1'
---
apiVersion: v1
kind: Pod
metadata:
name: dpdk-2002
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-dpdk
spec:
restartPolicy: Never
containers:
- name: dpdk
image: krsna1729/dpdk:20.02
stdin: true
tty: true
command: [ "/bin/bash", "-c"]
args:
- ls -l /dev/vfio;
testpmd --no-huge -m 2048 -- --stats-period=10 --nb-port=1 --port-topology=chained --auto-start --total-num-mbufs=2048 --forward-mode=macswap;
securityContext:
capabilities:
add:
- IPC_LOCK
resources:
limits:
intel.com/sriov_vfio: '1'
@@ -0,0 +1,17 @@
FROM ubuntu:bionic as ubuntu-build
RUN apt-get update && \
apt-get -y install \
build-essential \
git \
libnuma-dev
ARG DPDK_VER='master'
ENV DPDK_DIR='/dpdk'
ENV RTE_TARGET='x86_64-native-linuxapp-gcc'
RUN git clone -b $DPDK_VER -q --depth 1 http://dpdk.org/git/dpdk-stable $DPDK_DIR 2>&1
RUN cd ${DPDK_DIR} && \
sed -ri 's,(IGB_UIO=).*,\1n,' config/common_linux* && \
sed -ri 's,(KNI_KMOD=).*,\1n,' config/common_linux* && \
make config T=x86_64-native-linuxapp-gcc && \
make -j $CPUS
ENV PATH="$PATH:$DPDK_DIR/build/app/"
+1 -1
View File
@@ -170,7 +170,7 @@ function cni() {
function metrics() { function metrics() {
METRICS_VER="${1:-$METRICS_VER}" METRICS_VER="${1:-$METRICS_VER}"
METRICS_URL="https://github.com/kubernetes-incubator/metrics-server.git" METRICS_URL="https://github.com/kubernetes-sigs/metrics-server.git"
METRICS_DIR="1-core-metrics" METRICS_DIR="1-core-metrics"
get_repo "${METRICS_URL}" "${METRICS_DIR}/overlays/${METRICS_VER}" get_repo "${METRICS_URL}" "${METRICS_DIR}/overlays/${METRICS_VER}"
set_repo_version "${METRICS_VER}" "${METRICS_DIR}/overlays/${METRICS_VER}/metrics-server" set_repo_version "${METRICS_VER}" "${METRICS_DIR}/overlays/${METRICS_VER}/metrics-server"
+2 -1
View File
@@ -161,7 +161,8 @@ function setup_proxy() {
echo "Warning, failed to find /etc/profile.d/proxy.sh to edit no_proxy line" echo "Warning, failed to find /etc/profile.d/proxy.sh to edit no_proxy line"
fi fi
cat <<EOF | sudo bash -c "cat > /usr/lib/systemd/system.conf.d/proxy.conf" sudo mkdir -p /etc/systemd/system.conf.d
cat <<EOF | sudo bash -c "cat > /etc/systemd/system.conf.d/proxy.conf"
[Manager] [Manager]
DefaultEnvironment="HTTP_PROXY=${http_proxy}" DefaultEnvironment="HTTP_PROXY=${http_proxy}"
DefaultEnvironment="HTTPS_PROXY=${https_proxy}" DefaultEnvironment="HTTPS_PROXY=${https_proxy}"
+41 -118
View File
@@ -1,160 +1,83 @@
* [Metric testing for scaling on Kubernetes.](#metric-testing-for-scaling-on-kubernetes)
* [Results storage and analysis](#results-storage-and-analysis)
* [Developers](#developers)
* [Metrics gathering](#metrics-gathering)
* [`collectd` statistics](#collectd-statistics)
* [privileged statistics pods](#privileged-statistics-pods)
* [Configuring constant 'loads'](#configuring-constant-loads)
# Metric testing for scaling on Kubernetes. # Metric testing for scaling on Kubernetes.
This folder contains tools to aid in measuring the scaling capabilities of This folder contains tools to aid in measuring the scaling capabilities of
Kubernetes clusters. Kubernetes clusters.
Primarily these tools were designed to measure scaling of large number of pods on a single node, but
the code is structured to handle multiple nodes, and may also be useful in that scenario.
The tools tend to take one of two forms: The tools tend to take one of two forms:
- Tools to take measurements - Tools to launch jobs and take measurements
- Tools to analyse results - Tools to analyse results
For more details, see individual sub-folders. A brief summary of available tools For more details, see individual sub-folders. A brief summary of available tools
is below: is below:
| Tool | Description | | Folder | Description |
| ---- | ----------- | | ---- | ----------- |
| collectd | `collectd` based statistics/metrics gathering daemonset code | | collectd | `collectd` based statistics/metrics gathering daemonset code |
| lib | General library helper functions for forming and launching workloads, and storing results in a uniform manner to aid later analysis | | lib | General library helper functions for forming and launching workloads, and storing results in a uniform manner to aid later analysis |
| lib/cpu-load* | Routines to enable CPU load generation on a cluster | | lib/[cpu-load*](lib/cpu-load.md) | Helper functions to enable CPU load generation on a cluster whilst under test |
| report | Rmarkdown based report generator, used to produce a PDF comparison report of 1 or more sets of results | | [report](report/README.md) | Rmarkdown based report generator, used to produce a PDF comparison report of one or more sets of results |
| scaling | Tests to measure scaling, such as linear or parallel launching of pods | | [scaling](scaling/README.md) | Tests to measure scaling, such as linear or parallel launching of pods |
## Results storage and analysis ## Results storage and analysis
The tools generate JSON formatted results files via the `lib/json.bash` functions. The `metrics_json_save()` The tools generate JSON formatted results files via the [`lib/json.bash`](lib/json.bash) functions. The `metrics_json_save()`
function in that file has the ability to also `curl` or `socat` the JSON results to a database defined function has the ability to also `curl` or `socat` the JSON results to a database defined
by environment variables (see the file source for details). This method has been used to store results in by environment variables (see the file source for details). This method has been used to store results in
Elasticsearch and InfluxDB databases for instance, but should be adaptable to use with any REST API that accepts Elasticsearch and InfluxDB databases for instance, but should be adaptable to use with any REST API that accepts
JSON input. JSON input.
## Scaling execution ## Prerequisites
This section describes a complete step-by-step scaling execution up to results reporting by using `scaling/k8s_scale.sh` tool which launches a series of workloads and take memory metric measurements after each launch.
There are some basic pre-requisites required in order to run the test and process the results:
**Requirements**
* A Kubernetes cluster up and running (tested on v1.15.3). * A Kubernetes cluster up and running (tested on v1.15.3).
* `bc` and `jq` packages. * `bc` and `jq` packages.
* Docker (only for report generation). * Docker (only for report generation).
The steps to execute a run of the scaling framework are listed below, which need to be executed on the master node of a Kubernetes cluster to avoid network issues:
1. Clone `cloud-native-setup` repository into a preferred directory and change directory up to `cloud-native-setup/metrics`:
```sh
$ git clone https://github.com/clearlinux/cloud-native-setup.git
$ cd cloud-native-setup/metrics
```
2. Launch the execution by:
```sh
$ ./scaling/k8s_scale.sh
INFO: Initialising
command: bc: yes
command: jq: yes
INFO: Checking Kubernetes accessible
INFO: 1 Kubernetes nodes in 'Ready' state found
starting kubectl proxy
Starting to serve on 127.0.0.1:8090
daemonset.apps/stats created
Waiting for daemon set "stats" rollout to finish: 0 of 1 updated pods are available...
daemon set "stats" successfully rolled out
INFO: Running test
INFO: And grab some stats
INFO: idle [98.49] free [29031100] launch [0] node [clr-30f01b5149ba4ab8b05a7ee03b6812a5] inodes_free [31103039]
INFO: Testing replicas 1 of 20
INFO: Content of runtime_command=:/@RUNTIMECLASS@/d
...
```
The above execution might take about 4min because it launch up to 20 pods by default and takes measurements for CPU utilization, memory utilization and pod boot time, finally it will generate a `k8s-scaling.json` result file at `result` directory.
**Note**: to test the launch of pods concurrently, `k8s_parallel.sh` may be used. For quicker testing, `k8s_scale_rapid.sh` can be used in place of `k8s_scale.sh`. The rest of the launch instructions remain consistent other than script name.
**Note**: by default the scaling framework makes call to the Kubernetes API directly so, if facing connectivity issues verify that `kubelet` service's proxies and `no_proxy` environment variable are properly setup.
**Note**: by default the scaling framework uses default values for all its required variables, which can be checked through `scaling/k8s_scale.sh -h` and updated when launching the execution, i.e.:
```
$ ./scaling/k8s_scale.sh -h
Usage: ./scaling/k8s_scale.sh [-h] [options]
Description:
Launch a series of workloads and take memory metric measurements after
each launch.
Options:
-h, Help page.
Environment variables:
Name (default)
Description
TEST_NAME (k8s scaling)
Can be set to over-ride the default JSON results filename
NUM_PODS (20)
Number of pods to launch
STEP (1)
Number of pods to launch per cycle
wait_time (30)
Seconds to wait for pods to become ready
delete_wait_time (600)
Seconds to wait for all pods to be deleted
settle_time (5)
Seconds to wait after pods ready before taking measurements
use_api (yes)
specify yes or no to use the API to launch pods
grace (30)
specify the grace period in seconds for workload pod termination
$ use_api=no ./scaling/k8s_scale.sh
```
The steps to generate the result report are listed below:
1. Having the `results/k8s-scaling.json` result file, create a subdirectory in the `results` directory with a preferred name and copy the `k8s-scaling.json` file into it, so the file distribution looks like:
```sh
$ tree result
results/
└── scaling
└── k8s-scaling.json
```
**Note**: if `k8s_scale_rapid.sh` was run instead of `k8s_scale.sh`, that the `<node_name>.tar.gz` files that appear in the results directory also need to be copied into the newly created subdirectory. And the results file is named `k8s-rapid.json` rather than `k8s-scaling.json`.
If k8s_parallel.sh was run, the results file is named `k8s-parallel.json` rather than `k8s-scaling.json`.
2. Launch the report generation by:
```sh
./report/makereport.sh
```
**Note**: the first time you launch the report generation it will build a docker container to generate the reports and this process can take several minutes. Subsequent runs will be much faster.
The above execution will generate a `report/output` directory with the final reports, such as:
```sh
$ tree report/output/
report/output/
├── dut-1.png
├── metrics_report.pdf
├── scaling-1.png
├── scaling-2.png
├── scaling-3.png
└── scaling-4.png
```
More details about result reporting can be reviewed at [`report`](./report) directory.
# Developers # Developers
This section provides some details of how the code is structured and configured. This may be of use whilst modifying Below are some architecture and internal details of how the code is structured and configured. This will be
existing or creating new tests. helpful for improving, modifying or submitting fixes to the code base.
## Metrics gathering ## Metrics gathering
Metrics can be gathered using either a daemonset deployment of privileged pods used to gather statistics directly from the nodes using a combination of `mpstat`, `free` and `df`, or a daemonset deployment based around `collectd`. Metrics can be gathered using either a daemonset deployment of privileged pods used to gather statistics
directly from the nodes using a combination of `mpstat`, `free` and `df`, or a daemonset deployment based
around `collectd`. The general recommendation is to use the `collectd` based collection if possible, as it
is more efficient, as the system does not have to poll and wait for results, and thus executes the test
cycle faster. The `collectd` results are collected asyncronously, and the report generator code later
aligns the results with the pod execution in the timeline.
### `collectd` statistics ### `collectd` statistics
The `collected` based code can be found in the `collectd` subdirectory. It uses the `collected` configuration found in the `collectd.conf` file to gather statistics, and store the results on the nodes themselves whilst tests are running. At the end of the test, the results are copied from the nodes and stored in the results directory for later processing. The `collected` based code can be found in the `collectd` subdirectory. It uses the `collected` configuration
found in the `collectd.conf` file to gather statistics, and store the results on the nodes themselves whilst
tests are running. At the end of the test, the results are copied from the nodes and stored in the results
directory for later processing.
The `collectd` statistics are only configured and gathered if the environment variable `SMF_USE_COLLECTD` is set to non-empty by the test code (that is, only enabled upon request). The `collectd` statistics are only configured and gathered if the environment variable `SMF_USE_COLLECTD`
is set to non-empty by the test code (that is, it is only enabled upon request).
### privileged statistics pods ### privileged statistics pods
The privileged statistics pods `YAML` can be found in the `scaling/stats.yaml` file. An example of how to invoke and use this daemonset to extract statistics can be found in the `scaling/k8s_scale.sh` file. The privileged statistics pods `YAML` can be found in the [`scaling/stats.yaml`](scaling/stats.yaml) file.
An example of how to invoke and use this daemonset to extract statistics can be found in the
[`scaling/k8s_scale.sh`](scaling/k8s_scale.sh) file.
## Configuring constant 'loads' ## Configuring constant 'loads'
The framework includes some tooling to assist in setting up constant pre-defined 'loads' across the cluster to aid evaluation of their impacts on the scaling metrics. The framework includes some tooling to assist in setting up constant pre-defined 'loads' across the cluster
to aid evaluation of their impacts on the scaling metrics. See the [cpu-load documentation](lib/cpu-load.md)
### CPU load generator for more information.
Details of how to configure a constant CPU load are detailed in the [cpu-load documentation](lib/cpu-load.md).
+14 -6
View File
@@ -12,13 +12,23 @@ collectd_pod="collectd"
init_stats() { init_stats() {
local wait_time=$1 local wait_time=$1
# create collectd-config configmap # create collectd-config configmap, delete old if there is one
kubectl get configmap collectd-config >/dev/null 2>&1 && kubectl delete configmap collectd-config
kubectl create configmap collectd-config --from-file=${COLLECTD_DIR}/collectd.conf kubectl create configmap collectd-config --from-file=${COLLECTD_DIR}/collectd.conf
# if there is collectd daemonset already running, delete it
# to make sure that the latest configmap will be used.
kubectl get daemonset collectd >/dev/null 2>&1 && kubectl delete daemonset --wait=true --timeout=${delete_wait_time}s "${collectd_pod}"
# Launch our stats gathering pod # Launch our stats gathering pod
kubectl apply -f ${COLLECTD_DIR}/${collectd_pod}.yaml kubectl apply -f ${COLLECTD_DIR}/${collectd_pod}.yaml
kubectl rollout status --timeout=${wait_time}s daemonset/${collectd_pod} kubectl rollout status --timeout=${wait_time}s daemonset/${collectd_pod}
# clear existing collectd output
while read -u 3 name node; do
kubectl exec -ti $name -- sh -c "rm -rf /mnt/opt/collectd/run/localhost/*"
done 3< <(kubectl get pods --selector name=collectd-pods -o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"')
# attempting to provide buffer for collectd to be installed and running, # attempting to provide buffer for collectd to be installed and running,
# and CPU collection to build adequate history # and CPU collection to build adequate history
sleep 12 sleep 12
@@ -30,11 +40,9 @@ cleanup_stats() {
# get logs before shutting down stats daemonset # get logs before shutting down stats daemonset
while read -u 3 name node; do while read -u 3 name node; do
kubectl exec -ti $name -- sh -c "cd /opt/collectd; tar -czvf localhost.tar.gz localhost" kubectl exec -ti $name -- sh -c "cd /mnt/opt/collectd/run; rm -f ../localhost.tar.gz; tar -czvf ../localhost.tar.gz localhost"
# make a backup on the host in-case collection fail kubectl cp $name:/mnt/opt/collectd/localhost.tar.gz ${RESULT_DIR}/${node}.tar.gz
kubectl exec -ti $name -- sh -c "mkdir -p /mnt/opt/collectd" kubectl exec -ti $name -- sh -c "rm -rf /mnt/opt/collectd/run"
kubectl exec -ti $name -- sh -c "cp /opt/collectd/localhost.tar.gz /mnt/opt/collectd/localhost.tar.gz"
kubectl cp $name:/opt/collectd/localhost.tar.gz ${RESULT_DIR}/${node}.tar.gz
done 3< <(kubectl get pods --selector name=collectd-pods -o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"') done 3< <(kubectl get pods --selector name=collectd-pods -o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"')
kubectl delete daemonset --wait=true --timeout=${delete_wait_time}s "${collectd_pod}" || true kubectl delete daemonset --wait=true --timeout=${delete_wait_time}s "${collectd_pod}" || true
+1 -1
View File
@@ -17,7 +17,7 @@ Hostname localhost
ValuesPercentage true ValuesPercentage true
</Plugin> </Plugin>
<Plugin "csv"> <Plugin "csv">
DataDir "/opt/collectd" DataDir "/mnt/opt/collectd/run"
StoreRates true StoreRates true
</Plugin> </Plugin>
<Plugin "interface"> <Plugin "interface">
+2 -2
View File
@@ -13,10 +13,10 @@
# We would have used the 'verse' base, that already has some of the docs processing # We would have used the 'verse' base, that already has some of the docs processing
# installed, but I could not figure out how to add in the extra bits we needed to # installed, but I could not figure out how to add in the extra bits we needed to
# the lite tex version is uses. # the lite tex version is uses.
FROM rocker/tidyverse:latest FROM rocker/tidyverse:3.6.0
# Version of the Dockerfile # Version of the Dockerfile
LABEL DOCKERFILE_VERSION="1.0" LABEL DOCKERFILE_VERSION="1.1"
# Without this some of the package installs stop to try and ask questions... # Without this some of the package installs stop to try and ask questions...
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
@@ -13,25 +13,28 @@ suppressMessages(library(jsonlite)) # to load the data.
suppressMessages(library(scales)) # For de-science notation of axis suppressMessages(library(scales)) # For de-science notation of axis
library(tibble) # tibbles for tidy data library(tibble) # tibbles for tidy data
testnames=c(
render_collectd_scaling <- function()
{
testnames=c(
"k8s-rapid.*" "k8s-rapid.*"
) )
podbootdata=c() # Track per-launch data podbootdata=c() # Track per-launch data
cpuidledata=c() # Track cpu idle data per nodes cpuidledata=c() # Track cpu idle data per nodes
memfreedata=c() # Track mem free data for nodes memfreedata=c() # Track mem free data for nodes
inodefreedata=c() # Track inode free data for nodes inodefreedata=c() # Track inode free data for nodes
ifpacketdata=c() # Track interface packet data for nodes ifpacketdata=c() # Track interface packet data for nodes
ifoctetdata=c() # Track interface octets data for nodes ifoctetdata=c() # Track interface octets data for nodes
ifdropdata=c() # Track interface dropped data for nodes ifdropdata=c() # Track interface dropped data for nodes
iferrordata=c() # Track interface errors data for nodes iferrordata=c() # Track interface errors data for nodes
memstats=c() # Statistics for memory usage memstats=c() # Statistics for memory usage
cpustats=c() # Statistics for cpu usage cpustats=c() # Statistics for cpu usage
bootstats=c() # Statistics for boot (launch) times bootstats=c() # Statistics for boot (launch) times
inodestats=c() # Statistics for inode usage inodestats=c() # Statistics for inode usage
# iterate over every set of results (test run) # iterate over every set of results (test run)
for (currentdir in resultdirs) { for (currentdir in resultdirs) {
# For every results file we are interested in evaluating # For every results file we are interested in evaluating
for (testname in testnames) { for (testname in testnames) {
matchdir=paste(inputdir, currentdir, sep="") matchdir=paste(inputdir, currentdir, sep="")
@@ -385,7 +388,6 @@ for (currentdir in resultdirs) {
"avg_inode"=round(inodetotal/num_pods, 4) "avg_inode"=round(inodetotal/num_pods, 4)
) )
inodestats=rbind(inodestats, local_inodes) inodestats=rbind(inodestats, local_inodes)
}
# And collect up our rows into our global table of all results # And collect up our rows into our global table of all results
# These two tables *should* be the source of all the data we need to # These two tables *should* be the source of all the data we need to
@@ -399,23 +401,36 @@ for (currentdir in resultdirs) {
ifdropdata=rbind(ifdropdata, interface_dropped_data) ifdropdata=rbind(ifdropdata, interface_dropped_data)
iferrordata=rbind(iferrordata, interface_errors_data) iferrordata=rbind(iferrordata, interface_errors_data)
} }
} }
}
# It's nice to show the graphs in Gb, at least for any decent sized test # Check we actually found some JSON results files.
# run, so make a new column with that pre-divided data in it for us to use. if ( length(bootstats) == 0 ) {
memfreedata$mem_free_gb = memfreedata$value/(1024*1024*1024) cat("No results files found for rapid scaling tests\n\n")
# And show the boot times in seconds, not ms return()
podbootdata$launch_time_s = podbootdata$launch_time/1000.0 }
# And then check we found at least some matching node collectd data.
if ( length(memfreedata) == 0 ) {
cat("No collectd data found for rapid scaling tests\n\n")
return()
}
# It's nice to show the graphs in Gb, at least for any decent sized test
# run, so make a new column with that pre-divided data in it for us to use.
memfreedata$mem_free_gb = memfreedata$value/(1024*1024*1024)
# And show the boot times in seconds, not ms
podbootdata$launch_time_s = podbootdata$launch_time/1000.0
########### Output memory page ############## ########### Output memory page ##############
mem_stats_plot = suppressWarnings(ggtexttable(data.frame(memstats), mem_stats_plot = suppressWarnings(ggtexttable(data.frame(memstats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
mem_scale = (max(memfreedata$value) / (1024*1024*1024)) / max(podbootdata$n_pods) mem_scale = (max(memfreedata$value) / (1024*1024*1024)) / max(podbootdata$n_pods)
mem_line_plot <- ggplot() + mem_line_plot <- ggplot() +
geom_line(data=memfreedata, geom_line(data=memfreedata,
aes(s_offset, mem_free_gb, colour=interaction(testname, node), aes(s_offset, mem_free_gb, colour=interaction(testname, node),
group=interaction(testname, node)), group=interaction(testname, node)),
@@ -438,23 +453,23 @@ mem_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page1 = grid.arrange( page1 = grid.arrange(
mem_line_plot, mem_line_plot,
mem_stats_plot, mem_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output cpu page ############## ########## Output cpu page ##############
cpu_stats_plot = suppressWarnings(ggtexttable(data.frame(cpustats), cpu_stats_plot = suppressWarnings(ggtexttable(data.frame(cpustats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
cpu_scale = max(cpuidledata$value) / max(podbootdata$n_pods) cpu_scale = max(cpuidledata$value) / max(podbootdata$n_pods)
cpu_line_plot <- ggplot() + cpu_line_plot <- ggplot() +
geom_line(data=cpuidledata, geom_line(data=cpuidledata,
aes(x=s_offset, y=value, colour=interaction(testname, node), aes(x=s_offset, y=value, colour=interaction(testname, node),
group=interaction(testname, node)), group=interaction(testname, node)),
@@ -477,22 +492,22 @@ cpu_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page2 = grid.arrange( page2 = grid.arrange(
cpu_line_plot, cpu_line_plot,
cpu_stats_plot, cpu_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output boot page ############## ########## Output boot page ##############
boot_stats_plot = suppressWarnings(ggtexttable(data.frame(bootstats), boot_stats_plot = suppressWarnings(ggtexttable(data.frame(bootstats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
boot_line_plot <- ggplot() + boot_line_plot <- ggplot() +
geom_line(data=podbootdata, geom_line(data=podbootdata,
aes(n_pods, launch_time_s, colour=testname, group=testname), aes(n_pods, launch_time_s, colour=testname, group=testname),
alpha=0.2) + alpha=0.2) +
@@ -502,23 +517,23 @@ boot_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page3 = grid.arrange( page3 = grid.arrange(
boot_line_plot, boot_line_plot,
boot_stats_plot, boot_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output inode page ############## ########## Output inode page ##############
inode_stats_plot = suppressWarnings(ggtexttable(data.frame(inodestats), inode_stats_plot = suppressWarnings(ggtexttable(data.frame(inodestats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
inode_scale = max(inodefreedata$value) / max(podbootdata$n_pods) inode_scale = max(inodefreedata$value) / max(podbootdata$n_pods)
inode_line_plot <- ggplot() + inode_line_plot <- ggplot() +
geom_line(data=inodefreedata, geom_line(data=inodefreedata,
aes(x=s_offset, y=value, colour=interaction(testname, node), aes(x=s_offset, y=value, colour=interaction(testname, node),
group=interaction(testname, node)), group=interaction(testname, node)),
@@ -541,19 +556,19 @@ inode_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page4 = grid.arrange( page4 = grid.arrange(
inode_line_plot, inode_line_plot,
inode_stats_plot, inode_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output interface page packets and octets ############## ########## Output interface page packets and octets ##############
ip_scale = max(c(max(ifpacketdata$tx, na.rm=TRUE), ip_scale = max(c(max(ifpacketdata$tx, na.rm=TRUE),
max(ifpacketdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods) max(ifpacketdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods)
interface_packet_line_plot <- ggplot() + interface_packet_line_plot <- ggplot() +
geom_line(data=ifpacketdata, geom_line(data=ifpacketdata,
aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"), aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"),
group=interaction(testname, node, name, "tx")), group=interaction(testname, node, name, "tx")),
@@ -584,9 +599,9 @@ interface_packet_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
oct_scale = max(c(max(ifoctetdata$tx, na.rm=TRUE), oct_scale = max(c(max(ifoctetdata$tx, na.rm=TRUE),
max(ifoctetdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods) max(ifoctetdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods)
interface_octet_line_plot <- ggplot() + interface_octet_line_plot <- ggplot() +
geom_line(data=ifoctetdata, geom_line(data=ifoctetdata,
aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"), aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"),
group=interaction(testname, node, name, "tx")), group=interaction(testname, node, name, "tx")),
@@ -617,21 +632,21 @@ interface_octet_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page5 = grid.arrange( page5 = grid.arrange(
interface_packet_line_plot, interface_packet_line_plot,
interface_octet_line_plot, interface_octet_line_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output interface page drops and errors ############## ########## Output interface page drops and errors ##############
# drops are often 0, so providing 1 so we won't scale by infinity # drops are often 0, so providing 1 so we won't scale by infinity
drop_scale = max(c(1, drop_scale = max(c(1,
max(ifdropdata$tx, na.rm=TRUE), max(ifdropdata$tx, na.rm=TRUE),
max(ifdropdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods) max(ifdropdata$rx, na.rm=TRUE))) / max(podbootdata$n_pods)
interface_drop_line_plot <- ggplot() + interface_drop_line_plot <- ggplot() +
geom_line(data=ifdropdata, geom_line(data=ifdropdata,
aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"), aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"),
group=interaction(testname, node, name, "tx")), group=interaction(testname, node, name, "tx")),
@@ -662,11 +677,11 @@ interface_drop_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
# errors are often 0, so providing 1 so we won't scale by infinity # errors are often 0, so providing 1 so we won't scale by infinity
error_scale = max(c(1, error_scale = max(c(1,
max(iferrordata$tx, na.rm=TRUE), max(iferrordata$tx, na.rm=TRUE),
max(iferrordata$rx, na.rm=TRUE))) / max(podbootdata$n_pods) max(iferrordata$rx, na.rm=TRUE))) / max(podbootdata$n_pods)
interface_error_line_plot <- ggplot() + interface_error_line_plot <- ggplot() +
geom_line(data=iferrordata, geom_line(data=iferrordata,
aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"), aes(x=s_offset, y=tx, colour=interaction(testname, node, name, "tx"),
group=interaction(testname, node, name, name, "tx")), group=interaction(testname, node, name, name, "tx")),
@@ -697,8 +712,11 @@ interface_error_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page6 = grid.arrange( page6 = grid.arrange(
interface_drop_line_plot, interface_drop_line_plot,
interface_error_line_plot, interface_error_line_plot,
ncol=1 ncol=1
) )
}
render_collectd_scaling()
+30 -20
View File
@@ -13,21 +13,24 @@ library(gridExtra) # together.
suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable. suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable.
suppressMessages(library(jsonlite)) # to load the data. suppressMessages(library(jsonlite)) # to load the data.
# A list of all the known results files we might find the information inside. render_dut_details <- function()
resultsfiles=c( {
# A list of all the known results files we might find the information inside.
resultsfiles=c(
"k8s-parallel.json", "k8s-parallel.json",
"k8s-scaling.json", "k8s-scaling.json",
"k8s-rapid.json" "k8s-rapid.json"
) )
data=c() data=c()
stats=c() stats=c()
stats_names=c() stats_names=c()
# For each set of results # For each set of results
for (currentdir in resultdirs) { for (currentdir in resultdirs) {
count=1 count=1
dirstats=c() dirstats=c()
datasetname=c()
for (resultsfile in resultsfiles) { for (resultsfile in resultsfiles) {
fname=paste(inputdir, currentdir, resultsfile, sep="/") fname=paste(inputdir, currentdir, resultsfile, sep="/")
if ( !file.exists(fname)) { if ( !file.exists(fname)) {
@@ -76,32 +79,39 @@ for (currentdir in resultdirs) {
} }
if ( length(dirstats) == 0 ) { if ( length(dirstats) == 0 ) {
warning(paste("No valid data found for directory ", currentdir)) cat(paste("No valid data found for directory ", currentdir, "\n\n"))
} }
# use plyr rbind.fill so we can combine disparate version info frames # use plyr rbind.fill so we can combine disparate version info frames
stats=rbind.fill(stats, dirstats) stats=rbind.fill(stats, dirstats)
stats_names=rbind(stats_names, datasetname) stats_names=rbind(stats_names, datasetname)
} }
rownames(stats) = stats_names if ( length(stats_names) == 0 ) {
cat("No system details found\n\n")
return()
}
# Rotate the tibble so we get data dirs as the columns rownames(stats) = stats_names
spun_stats = as_tibble(cbind(What=names(stats), t(stats)))
# Build us a text table of numerical results # Rotate the tibble so we get data dirs as the columns
# Set up as left hand justify, so the node data indent renders. spun_stats = as_tibble(cbind(What=names(stats), t(stats)))
tablefontsize=8
tbody.style = tbody_style(hjust=0, x=0.1, size=tablefontsize) # Build us a text table of numerical results
stats_plot = suppressWarnings(ggtexttable(data.frame(spun_stats, check.names=FALSE), # Set up as left hand justify, so the node data indent renders.
tablefontsize=8
tbody.style = tbody_style(hjust=0, x=0.1, size=tablefontsize)
stats_plot = suppressWarnings(ggtexttable(data.frame(spun_stats, check.names=FALSE),
theme=ttheme(base_size=tablefontsize, tbody.style=tbody.style), theme=ttheme(base_size=tablefontsize, tbody.style=tbody.style),
rows=NULL rows=NULL
)) ))
# It may seem odd doing a grid of 1x1, but it should ensure we get a uniform format and # It may seem odd doing a grid of 1x1, but it should ensure we get a uniform format and
# layout to match the other charts and tables in the report. # layout to match the other charts and tables in the report.
master_plot = grid.arrange( master_plot = grid.arrange(
stats_plot, stats_plot,
nrow=1, nrow=1,
ncol=1 ) ncol=1 )
}
render_dut_details()
@@ -0,0 +1,26 @@
library('elasticsearchr')
for_scaling <- query('{
"bool": {
"must": [
{ "match":
{
"test.testname": "k8s scaling"
}
}
]
}
}')
these_fields <- select_fields('{
"includes": [
"date.Date",
"k8s-scaling.BootResults.launch_time.Result",
"k8s-scaling.BootResults.n_pods.Result"
]
}')
sort_by_date <- sort_on('[{"date.Date": {"order": "asc"}}]')
x=elastic("http://192.168.0.111:9200", "logtest") %search% (for_scaling + sort_by_date + these_fields)
@@ -32,7 +32,7 @@ This [test](https://github.com/clearlinux/cloud-native-setup/metrics/scaling/k8s
measures the time taken to launch and delete pods in parallel using a deployment. The times measures the time taken to launch and delete pods in parallel using a deployment. The times
are how long it takes for the whole deployment operation to complete. are how long it takes for the whole deployment operation to complete.
```{r parallel, echo=FALSE, fig.cap="K8S parallel pods"} ```{r parallel, echo=FALSE, fig.cap="K8S parallel pods", results='asis'}
source('parallel.R') source('parallel.R')
``` ```
@@ -57,7 +57,7 @@ This table describes the test system details, as derived from the information co
in the test results files. in the test results files.
```{r dut, echo=FALSE, fig.cap="System configuration details"} ```{r dut, echo=FALSE, fig.cap="System configuration details", results='asis'}
source('dut-details.R') source('dut-details.R')
``` ```
@@ -67,6 +67,6 @@ source('dut-details.R')
This table describes node details within the Kubernetes cluster that have been used for test. This table describes node details within the Kubernetes cluster that have been used for test.
```{r node, echo=FALSE, fig.cap="Node information within Kubernetes cluster"} ```{r node, echo=FALSE, fig.cap="Node information within Kubernetes cluster", results='asis'}
source('node-info.R') source('node-info.R')
``` ```
+33 -20
View File
@@ -13,21 +13,25 @@ library(gridExtra) # together.
suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable. suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable.
suppressMessages(library(jsonlite)) # to load the data. suppressMessages(library(jsonlite)) # to load the data.
# A list of all the known results files we might find the information inside. render_node_info <- function()
resultsfiles=c( {
# A list of all the known results files we might find the information inside.
resultsfiles=c(
"k8s-scaling.json" "k8s-scaling.json"
) )
stats=c() stats=c()
stats_names=c() stats_names=c()
max_char_name_node=18 datasetname=c()
complete_data=c()
max_char_name_node=18
# list for each dirstats # list for each dirstats
dirstats_list=list() dirstats_list=list()
j=1 j=1
# For each set of results # For each set of results
for (currentdir in resultdirs) { for (currentdir in resultdirs) {
dirstats=c() dirstats=c()
for (resultsfile in resultsfiles) { for (resultsfile in resultsfiles) {
fname=paste(inputdir, currentdir, resultsfile, sep="/") fname=paste(inputdir, currentdir, resultsfile, sep="/")
@@ -73,23 +77,32 @@ for (currentdir in resultdirs) {
} }
if ( length(complete_data) == 0 ) { if ( length(complete_data) == 0 ) {
warning(paste("No valid data found for directory ", currentdir)) cat(paste("No valid data found for directory ", currentdir, "\n\n"))
} }
# use plyr rbind.fill so we can combine disparate version info frames # use plyr rbind.fill so we can combine disparate version info frames
stats=rbind.fill(stats, complete_data) stats=rbind.fill(stats, complete_data)
stats_names=rbind(stats_names, datasetname) stats_names=rbind(stats_names, datasetname)
} }
# Build us a text table of numerical results
# Set up as left hand justify, so the node data indent renders. if ( length(stats_names) == 0 ) {
tablefontsize=8 cat("No node stats found\n\n");
tbody.style = tbody_style(hjust=0, x=0.1, size=tablefontsize) return()
stats_plot = suppressWarnings(ggtexttable(data.frame(complete_data, check.names=FALSE), }
# Build us a text table of numerical results
# Set up as left hand justify, so the node data indent renders.
tablefontsize=8
tbody.style = tbody_style(hjust=0, x=0.1, size=tablefontsize)
stats_plot = suppressWarnings(ggtexttable(data.frame(complete_data, check.names=FALSE),
theme=ttheme(base_size=tablefontsize, tbody.style=tbody.style), theme=ttheme(base_size=tablefontsize, tbody.style=tbody.style),
rows=NULL)) rows=NULL))
# It may seem odd doing a grid of 1x1, but it should ensure we get a uniform format and # It may seem odd doing a grid of 1x1, but it should ensure we get a uniform format and
# layout to match the other charts and tables in the report. # layout to match the other charts and tables in the report.
master_plot = grid.arrange(stats_plot, master_plot = grid.arrange(stats_plot,
nrow=1, nrow=1,
ncol=1 ) ncol=1 )
}
render_node_info()
+28 -18
View File
@@ -13,20 +13,22 @@ suppressMessages(suppressWarnings(library(ggpubr))) # for ggtexttable.
suppressMessages(library(jsonlite)) # to load the data. suppressMessages(library(jsonlite)) # to load the data.
suppressMessages(library(scales)) # For de-science notation of axis suppressMessages(library(scales)) # For de-science notation of axis
testnames=c( render_parallel <- function()
{
testnames=c(
"k8s-parallel*" "k8s-parallel*"
) )
data=c() data=c()
stats=c() stats=c()
rstats=c() rstats=c()
rstats_names=c() rstats_names=c()
cstats=c() cstats=c()
cstats_names=c() cstats_names=c()
skip_points_enable_smooth=0 # Should we draw the points as well as lines on the graphs. skip_points_enable_smooth=0 # Should we draw the points as well as lines on the graphs.
for (currentdir in resultdirs) { for (currentdir in resultdirs) {
dirstats=c() dirstats=c()
for (testname in testnames) { for (testname in testnames) {
matchdir=paste(inputdir, currentdir, sep="") matchdir=paste(inputdir, currentdir, sep="")
@@ -73,10 +75,16 @@ for (currentdir in resultdirs) {
data=rbind(data, cdata) data=rbind(data, cdata)
} }
} }
} }
# Show how boot time changed # If we found nothing to process, quit early and nicely
boot_line_plot <- ggplot( data=data, aes(npod, boot_time, colour=testname, group=dataset)) + if ( length(data) == 0 ) {
cat("No results files found for parallel tests\n\n")
return()
}
# Show how boot time changed
boot_line_plot <- ggplot( data=data, aes(npod, boot_time, colour=testname, group=dataset)) +
geom_line( alpha=0.2) + geom_line( alpha=0.2) +
xlab("parallel pods") + xlab("parallel pods") +
ylab("Boot time (s)") + ylab("Boot time (s)") +
@@ -94,8 +102,8 @@ boot_line_plot <- ggplot( data=data, aes(npod, boot_time, colour=testname, group
boot_line_plot_zero = boot_line_plot + ylim(0, NA) + boot_line_plot_zero = boot_line_plot + ylim(0, NA) +
ggtitle("Deployment boot time (0 index)") ggtitle("Deployment boot time (0 index)")
# Show how boot time changed # Show how boot time changed
delete_line_plot <- ggplot( data=data, aes(npod, delete_time, colour=testname, group=dataset)) + delete_line_plot <- ggplot( data=data, aes(npod, delete_time, colour=testname, group=dataset)) +
geom_line(alpha=0.2) + geom_line(alpha=0.2) +
xlab("parallel pods") + xlab("parallel pods") +
ylab("Delete time (s)") + ylab("Delete time (s)") +
@@ -113,13 +121,15 @@ delete_line_plot <- ggplot( data=data, aes(npod, delete_time, colour=testname, g
delete_line_plot_zero = delete_line_plot + ylim(0, NA) + delete_line_plot_zero = delete_line_plot + ylim(0, NA) +
ggtitle("Deployment deletion time (0 index)") ggtitle("Deployment deletion time (0 index)")
# See https://www.r-bloggers.com/ggplot2-easy-way-to-mix-multiple-graphs-on-the-same-page/ for # See https://www.r-bloggers.com/ggplot2-easy-way-to-mix-multiple-graphs-on-the-same-page/ for
# excellent examples # excellent examples
master_plot = grid.arrange( master_plot = grid.arrange(
boot_line_plot_zero, boot_line_plot_zero,
delete_line_plot_zero, delete_line_plot_zero,
boot_line_plot, boot_line_plot,
delete_line_plot, delete_line_plot,
nrow=2, nrow=2,
ncol=2 ) ncol=2 )
}
render_parallel()
+9
View File
@@ -0,0 +1,9 @@
suppressMessages(library(jsonlite)) # to load the data.
options(digits=22)
x=fromJSON('{"ns": 1567002188374607769}')
print(x)
print(fromJSON('{"ns": 1567002188374607769}'), digits=22)
+54 -42
View File
@@ -13,19 +13,21 @@ suppressMessages(library(jsonlite)) # to load the data.
suppressMessages(library(scales)) # For de-science notation of axis suppressMessages(library(scales)) # For de-science notation of axis
library(tibble) # tibbles for tidy data library(tibble) # tibbles for tidy data
testnames=c( render_tidy_scaling <- function()
{
testnames=c(
"k8s-scaling.*" "k8s-scaling.*"
) )
bootdata=c() # Track per-launch data bootdata=c() # Track per-launch data
nodedata=c() # Track node status data nodedata=c() # Track node status data
memstats=c() # Statistics for memory usage memstats=c() # Statistics for memory usage
cpustats=c() # Statistics for cpu usage cpustats=c() # Statistics for cpu usage
bootstats=c() # Statistics for boot (launch) times bootstats=c() # Statistics for boot (launch) times
inodestats=c() # Statistics for inode usage inodestats=c() # Statistics for inode usage
# iterate over every set of results (test run) # iterate over every set of results (test run)
for (currentdir in resultdirs) { for (currentdir in resultdirs) {
# For every results file we are interested in evaluating # For every results file we are interested in evaluating
for (testname in testnames) { for (testname in testnames) {
matchdir=paste(inputdir, currentdir, sep="") matchdir=paste(inputdir, currentdir, sep="")
@@ -198,27 +200,34 @@ for (currentdir in resultdirs) {
nodedata=rbind(nodedata, local_nodedata, make.row.names=FALSE) nodedata=rbind(nodedata, local_nodedata, make.row.names=FALSE)
} }
} }
} }
# It's nice to show the graphs in Gb, at least for any decent sized test # Check if we got any stats at all by checking the memstats data. If we found no data,
# run, so make a new column with that pre-divided data in it for us to use. # abort early and nicely
nodedata$mem_free_gb = nodedata$mem_free/(1024*1024) if ( length(memstats) == 0 ) {
nodedata$mem_used_gb = nodedata$mem_used/(1024*1024) cat("No results files found for scaling tests\n\n")
# And show the boot times in seconds, not mS return()
bootdata$launch_time_s = bootdata$launch_time/1000 }
# The labels get messed up by us using an 'if' in the aes() - correct it by # It's nice to show the graphs in Gb, at least for any decent sized test
# using the same 'if' to assign what we really want to use for the labels. # run, so make a new column with that pre-divided data in it for us to use.
colour_label=(if(length(resultdirs)> 1) "testname" else "node") nodedata$mem_free_gb = nodedata$mem_free/(1024*1024)
nodedata$mem_used_gb = nodedata$mem_used/(1024*1024)
# And show the boot times in seconds, not mS
bootdata$launch_time_s = bootdata$launch_time/1000
# The labels get messed up by us using an 'if' in the aes() - correct it by
# using the same 'if' to assign what we really want to use for the labels.
colour_label=(if(length(resultdirs)> 1) "testname" else "node")
########## Output memory page ############## ########## Output memory page ##############
mem_stats_plot = suppressWarnings(ggtexttable(data.frame(memstats), mem_stats_plot = suppressWarnings(ggtexttable(data.frame(memstats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
mem_line_plot <- ggplot(data=nodedata, aes(n_pods, mem_line_plot <- ggplot(data=nodedata, aes(n_pods,
mem_free_gb, mem_free_gb,
colour=(if (length(resultdirs) > 1) testname else node), colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) + group=interaction(testname, node))) +
@@ -232,22 +241,22 @@ mem_line_plot <- ggplot(data=nodedata, aes(n_pods,
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page1 = grid.arrange( page1 = grid.arrange(
mem_line_plot, mem_line_plot,
mem_stats_plot, mem_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output cpu page ############## ########## Output cpu page ##############
cpu_stats_plot = suppressWarnings(ggtexttable(data.frame(cpustats), cpu_stats_plot = suppressWarnings(ggtexttable(data.frame(cpustats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
cpu_line_plot <- ggplot(data=nodedata, aes(n_pods, cpu_line_plot <- ggplot(data=nodedata, aes(n_pods,
idle, idle,
colour=(if (length(resultdirs) > 1) testname else node), colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) + group=interaction(testname, node))) +
@@ -260,22 +269,22 @@ cpu_line_plot <- ggplot(data=nodedata, aes(n_pods,
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page2 = grid.arrange( page2 = grid.arrange(
cpu_line_plot, cpu_line_plot,
cpu_stats_plot, cpu_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output boot page ############## ########## Output boot page ##############
boot_stats_plot = suppressWarnings(ggtexttable(data.frame(bootstats), boot_stats_plot = suppressWarnings(ggtexttable(data.frame(bootstats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
boot_line_plot <- ggplot() + boot_line_plot <- ggplot() +
geom_line( data=bootdata, aes(n_pods, launch_time_s, colour=testname, group=testname), alpha=0.2) + geom_line( data=bootdata, aes(n_pods, launch_time_s, colour=testname, group=testname), alpha=0.2) +
geom_point( data=bootdata, aes(n_pods, launch_time_s, colour=interaction(testname, node), group=testname), alpha=0.6, size=0.6, stroke=0, shape=16) + geom_point( data=bootdata, aes(n_pods, launch_time_s, colour=interaction(testname, node), group=testname), alpha=0.6, size=0.6, stroke=0, shape=16) +
xlab("pods") + xlab("pods") +
@@ -284,22 +293,22 @@ boot_line_plot <- ggplot() +
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page3 = grid.arrange( page3 = grid.arrange(
boot_line_plot, boot_line_plot,
boot_stats_plot, boot_stats_plot,
ncol=1 ncol=1
) )
# pagebreak, as the graphs overflow the page otherwise # pagebreak, as the graphs overflow the page otherwise
cat("\n\n\\pagebreak\n") cat("\n\n\\pagebreak\n")
########## Output inode page ############## ########## Output inode page ##############
inode_stats_plot = suppressWarnings(ggtexttable(data.frame(inodestats), inode_stats_plot = suppressWarnings(ggtexttable(data.frame(inodestats),
theme=ttheme(base_size=10), theme=ttheme(base_size=10),
rows=NULL rows=NULL
)) ))
inode_line_plot <- ggplot(data=nodedata, aes(n_pods, inode_line_plot <- ggplot(data=nodedata, aes(n_pods,
inode_free, inode_free,
colour=(if (length(resultdirs) > 1) testname else node), colour=(if (length(resultdirs) > 1) testname else node),
group=interaction(testname, node))) + group=interaction(testname, node))) +
@@ -313,8 +322,11 @@ inode_line_plot <- ggplot(data=nodedata, aes(n_pods,
theme(legend.position="bottom") + theme(legend.position="bottom") +
theme(axis.text.x=element_text(angle=90)) theme(axis.text.x=element_text(angle=90))
page4 = grid.arrange( page4 = grid.arrange(
inode_line_plot, inode_line_plot,
inode_stats_plot, inode_stats_plot,
ncol=1 ncol=1
) )
}
render_tidy_scaling()
+118
View File
@@ -0,0 +1,118 @@
# Scaling metrics tests
This directory contains a number of scripts to perform a variety of system scaling tests.
The tests are described in their individual sections below.
Each test has a number of configurable options. Many of those options are common across all tests.
Those options are detailed in their own section below.
> **Note:** `k8s_scale_rapid.sh` is the most complete and upto date test. It is the only test to
> currently use the `collectd` data collection method. Other tests use a privileged container to
> gather statistics.
>
> If you find one of the other tests useful, please consider updating it and the corresponding report
> generation code to use the `collectd` method and send a Pull Request with your updates to this codebase.
## Global test configuration options
The following variables are settable for many of the tests. Check each individual tests help
for specifics and their individual default values.
| Variable | Default Value | Description |
| -------- | ------------- | ----------- |
| TEST_NAME | test dependant | Can be set to over-ride the default JSON results filename |
| NUM_PODS | 20 | Number of pods to launch |
| STEP | 1 | Number of pods to launch per cycle |
| wait_time | 30 | Seconds to wait for pods to become ready |
| delete_wait_time | 600 | Seconds to wait for all pods to be deleted |
| settle_time | 5 | Seconds to wait after pods ready before taking measurements |
| use_api | yes | specify yes or no to use the JSON API to launch pods (otherwise, launch via YAML) |
| grace | 30 | specify the grace period in seconds for workload pod termination |
| RUNTIME | unset | specify the `RuntimeClass` to use to launch the pods |
## k8s_parallel.sh
Measures pod create and delete times whilst increasing the number of pods launched in parallel.
The test works by creating and destroying deployments with the required number of replicas being scaled.
## k8s_scale_nc.sh
Measures pod response time using `nc` to test network connection response. Stores results as percentile
values. Is used to see if the response time latency and jitter is affected by scaling the number of pods.
## k8s_scale_net.sh
Measures pod response time to a `curl` HTTP get request from the K8S e2e `agnhost` image.
Used to measure if the 'ready to respond' time scales with the number of service ports in use.
## k8s_scale_rapid.sh
Measures how pod launch and the k8s system scales whilst launching more and more pods.
Uses the `collectd` method to gather a number of statistics, including:
- cpu usage
- memory usage
- network connections
- disk usage
- ipc stats
## k8s_scale.sh
The fore-runner to `k8s_scale_rapid.sh`, using the privileged pod method to gather statistics. It is recommended
to use `k8s_scale_rapid.sh` in preference if possible.
# Example
Below is a brief example of running the `k8s_scale_rapid.sh` test and generating a report from the results.
1. Run the test
The test will run against the default `kubectl` configured cluster.
```sh
$ ./scaling/k8s_scale.sh
```
Results are stored in the `results` directory. The results will comprise of one `JSON` file for the test, and
one `.tar.gz` file for each node found in the cluster.
> **Note:** Only the `collectd` based tests generate `.tar.gz` files. All other tests only generate a single
> `JSON` file for each run.
1. Move the results files
In order to generate the report, the results files should be moved into an appropriately named sub-directory.
The report generator can process and compare multiple sets of results. Each set of results should be placed
into its own sub-directory. The below example uses the name `run1` as an example:
```sh
$ cd results
$ mkdir run1
$ mv *.json run1
$ mv *.tar.gz run1
```
This sequence can be repeated to gather multiple test data sets. Place each data set in its own subdirectory.
The report generator will process and compare all data set subdirectories found in the `results` directory.
1. Generate the report
The report generator in the `report` subdirectory processes the sub-directories of the `results` directory
to produce a `PDF` report and individual `PNG` based graphs.. The report generator utilises `docker` to create
a docker image containing all the tooling necessary.
```sh
$ cd report
$ ./makereport.sh
...
$ tree output
output/
├── dut-1.png
├── metrics_report.pdf
├── scaling-1.png
├── scaling-2.png
├── scaling-3.png
└── scaling-4.png
```