├── .gitignore ├── 3rdparty ├── docker-grafana-influxdb │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── build │ ├── grafana-influxdb-wiring.sh │ ├── influxdb │ │ ├── LICENSE │ │ ├── config.toml │ │ └── run.sh │ ├── proxy_paste │ ├── set_grafana.sh │ ├── set_influxdb.sh │ ├── start_visualisation.sh │ └── supervisord.conf └── lib │ ├── curlcpp.tar.gz │ ├── gmock-1.7.0.tar.gz │ ├── pbjson.tar.gz │ ├── pwave │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── pwave.png │ ├── generator.hpp │ ├── noise.hpp │ ├── scenario.hpp │ └── tests │ │ └── pwave_test.cpp │ └── rapidjson-v1.0.2.tar.gz ├── CMakeLists.txt ├── Dockerfile ├── Doxyfile ├── LICENSE ├── README.md ├── docs ├── README.md ├── developer.md ├── images │ └── serenity_pipeline.png └── smoke_framework.md ├── scripts ├── cpplint.py ├── graphana_run.sh ├── influxdb_run.sh ├── lint.sh ├── pre-commit └── usage_generator.py └── src ├── bus ├── event_bus.cpp └── event_bus.hpp ├── contention_detectors ├── overload.cpp ├── overload.hpp ├── signal_analyzers │ ├── base.hpp │ ├── drop.cpp │ └── drop.hpp ├── signal_based.cpp └── signal_based.hpp ├── filters ├── correction_merger.hpp ├── cumulative.cpp ├── cumulative.hpp ├── ema.cpp ├── ema.hpp ├── executor_age.cpp ├── executor_age.hpp ├── ignore_new_executors.cpp ├── ignore_new_executors.hpp ├── pr_executor_pass.cpp ├── pr_executor_pass.hpp ├── too_low_usage.cpp ├── too_low_usage.hpp ├── utilization_threshold.cpp ├── utilization_threshold.hpp ├── valve.cpp └── valve.hpp ├── mesos_frameworks └── smoke_test │ ├── README.md │ ├── run_framework.sh │ ├── smoke_flags.hpp │ ├── smoke_job.hpp │ ├── smoke_queue.hpp │ ├── smoke_test_framework.cpp │ └── tasks │ ├── 1be_load.json │ ├── 1be_task.json │ ├── 1hp_and_1be_tasks.json │ └── 1hp_task.json ├── mesos_modules ├── qos_controller │ ├── serenity_controller.cpp │ ├── serenity_controller.hpp │ └── serenity_controller_module.cpp └── resource_estimator │ ├── serenity_estimator.cpp │ ├── serenity_estimator.hpp │ └── serenity_estimator_module.cpp ├── messages ├── serenity.hpp └── serenity.proto ├── observers ├── qos_correction.cpp ├── qos_correction.hpp ├── slack_resource.cpp ├── slack_resource.hpp └── strategies │ ├── base.hpp │ ├── cache_occupancy.cpp │ ├── cache_occupancy.hpp │ ├── cpu_contention.cpp │ ├── cpu_contention.hpp │ ├── kill_all.cpp │ ├── kill_all.hpp │ ├── seniority.cpp │ └── seniority.hpp ├── pipeline ├── estimator_pipeline.hpp ├── pipeline.hpp └── qos_pipeline.hpp ├── serenity ├── agent_utils.cpp ├── agent_utils.hpp ├── config.hpp ├── data_utils.hpp ├── default_vars.hpp ├── executor_map.hpp ├── executor_set.hpp ├── math_utils.hpp ├── metrics_helper.hpp ├── os_utils.hpp ├── resource_helper.cpp ├── resource_helper.hpp ├── serenity.hpp ├── utils.hpp ├── wid.cpp └── wid.hpp ├── tests ├── bus │ └── event_bus_tests.cpp ├── common │ ├── config_helper.hpp │ ├── mocks │ │ ├── mock_consumer.hpp │ │ ├── mock_filter.hpp │ │ ├── mock_multiple_consumer.hpp │ │ └── mock_sink.hpp │ ├── serenity.hpp │ ├── signal_helper.hpp │ ├── sinks │ │ ├── dummy_sink.hpp │ │ └── printer_sink.hpp │ ├── sources │ │ ├── json_source.cpp │ │ ├── json_source.hpp │ │ ├── json_source.proto │ │ └── mock_source.hpp │ └── usage_helper.hpp ├── contention_detectors │ ├── overload_test.cpp │ └── signal_analyzers │ │ └── drop_test.cpp ├── filters │ ├── correction_merger_test.cpp │ ├── ema_test.cpp │ ├── ignore_new_executors_test.cpp │ ├── pr_executor_pass_test.cpp │ ├── utilization_threshold_test.cpp │ └── valve_test.cpp ├── fixtures │ ├── baseline_smoke_test_resource_usage.json │ ├── be_start_json_test.json │ ├── ema │ │ ├── insufficient_metrics_test.json │ │ └── test.json │ ├── ignore_new_executors.json │ ├── pipeline │ │ ├── insufficient_metrics.json │ │ ├── ips_qos_one_drop_correction.json │ │ ├── qos_no_correction.json │ │ ├── qos_one_drop_correction.json │ │ └── sufficient_metrics.json │ ├── pr_executor_pass │ │ ├── insufficient_metrics_test.json │ │ └── test.json │ ├── qos │ │ └── average_usage.json │ ├── slack_estimator │ │ ├── max_oversubscription_fraction_test.json │ │ └── slack_calculation_test.json │ ├── start_json_test.json │ └── utilization_threshold │ │ ├── ok_load_test.json │ │ └── too_high_load_test.json ├── main.cpp ├── mesos_modules │ ├── qos_controller │ │ └── qos_controller_test.cpp │ └── resource_estimator │ │ └── estimator_test.cpp ├── observers │ ├── qos_correction_test.cpp │ ├── slack_resource_test.cpp │ └── strategies │ │ ├── cache_occupancy_strategy_test.cpp │ │ └── seniority_strategy_test.cpp ├── pipeline │ ├── estimator_pipeline_test.cpp │ └── qos_pipeline_test.cpp ├── serenity │ ├── agent_utils_tests.cpp │ ├── config_test.cpp │ ├── os_utils_tests.cpp │ ├── resource_helper_test.cpp │ └── serenity_tests.cpp ├── sources │ └── json_source_test.cpp └── time_series_export │ ├── backend │ └── influx_db9_test.cpp │ ├── resource_usage_ts_export_test.cpp │ └── slack_ts_export_test.cpp └── time_series_export ├── backend ├── influx_db9.cpp ├── influx_db9.hpp ├── time_series_backend.hpp └── time_series_record.hpp ├── resource_usage_ts_export.cpp ├── resource_usage_ts_export.hpp ├── slack_ts_export.cpp └── slack_ts_export.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | 3rdparty/lib/gmock-1.7.0/* 3 | 3rdparty/lib/pbjson/* 4 | 3rdparty/lib/curlcpp/* 5 | 3rdparty/lib/rapidjson-1.0.2/* 6 | m4/ 7 | Makefile.in 8 | aclocal.m4 9 | autom4te.cache/ 10 | Makefile.in 11 | aclocal.m4 12 | ar-lib 13 | autom4te.cache 14 | compile 15 | config.guess 16 | config.sub 17 | configure 18 | depcomp 19 | install-sh 20 | libtool.m4 21 | ltmain.sh 22 | ltoptions.m4 23 | ltsugar.m4 24 | ltversion.m4 25 | lt~obsolete.m4 26 | missing 27 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | ENV GRAFANA_VERSION 2.1.0-pre1.linux-x64 4 | ENV INFLUXDB_VERSION 0.8.8 5 | 6 | # Prevent some error messages 7 | ENV DEBIAN_FRONTEND noninteractive 8 | 9 | ENV TERM=xterm 10 | 11 | RUN apt-get -y update && apt-get -y upgrade 12 | 13 | # ---------------- # 14 | # Installation # 15 | # ---------------- # 16 | 17 | # Install all prerequisites 18 | RUN apt-get -y install wget nginx-light supervisor curl 19 | 20 | 21 | # Install Grafana to /src/grafana 22 | RUN mkdir -p src/grafana && cd src/grafana && \ 23 | wget http://grafanarel.s3.amazonaws.com/builds/grafana-${GRAFANA_VERSION}.tar.gz -O grafana.tar.gz && \ 24 | tar xzf grafana.tar.gz --strip-components=1 && rm grafana.tar.gz 25 | 26 | # Install InfluxDB 27 | RUN wget http://s3.amazonaws.com/influxdb/influxdb_${INFLUXDB_VERSION}_amd64.deb && \ 28 | dpkg -i influxdb_${INFLUXDB_VERSION}_amd64.deb && rm influxdb_${INFLUXDB_VERSION}_amd64.deb 29 | 30 | # ----------------- # 31 | # Configuration # 32 | # ----------------- # 33 | 34 | # Configure InfluxDB 35 | ADD influxdb/config.toml /etc/influxdb/config.toml 36 | ADD influxdb/run.sh /usr/local/bin/run_influxdb 37 | # These two databases have to be created. These variables are used by set_influxdb.sh and set_grafana.sh 38 | ENV PRE_CREATE_DB data grafana 39 | ENV INFLUXDB_DATA_USER data 40 | ENV INFLUXDB_DATA_PW data 41 | ENV INFLUXDB_GRAFANA_USER grafana 42 | ENV INFLUXDB_GRAFANA_PW grafana 43 | ENV ROOT_PW root 44 | 45 | # Configure Grafana 46 | ADD ./grafana-influxdb-wiring.sh /grafana-influxdb-wiring.sh 47 | 48 | # Configure nginx and supervisord 49 | ADD ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf 50 | 51 | # ----------- # 52 | # Cleanup # 53 | # ----------- # 54 | 55 | #RUN apt-get autoremove -y wget && \ 56 | # apt-get -y clean && \ 57 | # rm -rf /var/lib/apt/lists/* 58 | 59 | # ---------------- # 60 | # Expose Ports # 61 | # ---------------- # 62 | 63 | # Grafana 64 | EXPOSE 80 65 | 66 | # Grafana 67 | EXPOSE 3000 68 | 69 | # InfluxDB Admin server 70 | EXPOSE 8083 71 | 72 | # InfluxDB HTTP API 73 | EXPOSE 8086 74 | 75 | # InfluxDB HTTPS API 76 | EXPOSE 8084 77 | 78 | # -------- # 79 | # Run! # 80 | # -------- # 81 | 82 | CMD ["/usr/bin/supervisord"] -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/README.md: -------------------------------------------------------------------------------- 1 | docker-grafana-influxdb 2 | ======================= 3 | 4 | This image contains a sensible default configuration of InfluxDB and Grafana. It explicitly doesn't bundle an example dashboard. 5 | 6 | ### Using the Dashboard ### 7 | 8 | Once your container is running all you need to do is open your browser pointing to the host/port you just published and play with the dashboard at your wish. We hope that you have a lot of fun with this image and that it serves it's purpose of making your life easier. 9 | 10 | ### Building the image yourself ### 11 | 12 | The Dockerfile and supporting configuration files are available in this Github repository. This comes specially handy if you want to change any of the InfluxDB or Grafana settings, or simply if you want to know how the image was built. 13 | The repo also has `build`, `start` and `stop` scripts to make your workflow more pleasant. 14 | 15 | ### Configuring the settings ### 16 | 17 | The container exposes the following ports by default: 18 | 19 | - `80`: Grafana web interface. 20 | - `8083`: InfluxDB Admin web interface. 21 | - `8084`: InfluxDB HTTPS API (not usable by default). 22 | - `8086`: InfluxDB HTTP API. 23 | 24 | To start a container with your custom config: see `start` script. 25 | 26 | To change ports, consider the following: 27 | 28 | - `80`: edit `Dockerfile, ngingx/nginx.conf and start script`. 29 | - `8083`: edit: `Dockerfile, influxDB/config.toml and start script`. 30 | - `8084`: edit: to be announced. 31 | - `8086`: edit: `Dockerfile, influxDB/config.toml, grafana/config.js, set_influxdb.sh and start script`. 32 | 33 | ### Running container under boot2docker on Mac OS X ### 34 | Currently, there is an issue with boot2docker dicussed [here](https://github.com/kamon-io/docker-grafana-graphite/issues/5). To bypass this, change the last line in start script to the following to start the container: 35 | ```bash 36 | docker run -d -p 80:80 -p 8083:8083 -p 8084:8084 -p 8086:8086 --name grafana-influxdb_con grafana_influxdb 37 | ``` 38 | 39 | InfluxDB is configured by default with two databases. `grafana` DB for storing your Dashboard and `data` DB for storing your measurements. You can edit all default passwords in `Dockerfile`. If you wanna edit DB names, users and passwords, have a look at the following files: `grafana/config.js, set_grafana.sh, set_influxdb.sh and Dockerfile` 40 | 41 | HTTPS API wasn't tested yet, that's why it isn't configured. Some boilerplate code can be found in `Dockerfile and set_influxdb.sh`. Needs testing and possibly more. 42 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/build: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | docker rm grafana-influxdb_con 4 | docker rmi -f grafana_influxdb 5 | docker build -t grafana_influxdb . 6 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/influxdb/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -m 4 | 5 | CONFIG_FILE="/etc/influxdb/config.toml" 6 | 7 | echo "=> Starting InfluxDB ..." 8 | exec /usr/bin/influxdb -config=${CONFIG_FILE} 9 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/proxy_paste: -------------------------------------------------------------------------------- 1 | RUN echo 'Acquire::http { Proxy "http://proxy-chain.intel.com:911/"; };' >>/etc/apt/apt.conf.d/01proxy 2 | 3 | ENV HTTP_PROXY=http://proxy-chain.intel.com:911 4 | ENV http_proxy=http://proxy-chain.intel.com:911 5 | ENV HTTPS_PROXY=https://proxy-chain.intel.com:911 6 | ENV https_proxy=https://proxy-chain.intel.com:911 7 | ENV FTP_PROXY=http://proxy-chain.intel.com:911 8 | ENV ftp_proxy=http://proxy-chain.intel.com:911 9 | ENV NO_PROXY="localhost,172.28.38.23,127.0.0.1,192.168.122.1,10.0.2.15,*.example.com,*.intel.com,.intel.com,::1,ip6-loopback,/var/run/docker.sock" 10 | 11 | ENV no_proxy="localhost,172.28.38.23,127.0.0.1,192.168.122.1,10.0.2.15,*.example.com,*.intel.com,.intel.com,::1,ip6-loopback,/var/run/docker.sock" 12 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/set_grafana.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -f /.grafana_configured ]; then 5 | echo "=> grafana has been configured!" 6 | exit 0 7 | fi 8 | 9 | echo "=> Configuring grafana" 10 | sed -i -e "s/<--DATA_USER-->/${INFLUXDB_DATA_USER}/g" \ 11 | -e "s/<--DATA_PW-->/${INFLUXDB_DATA_PW}/g" \ 12 | -e "s/<--GRAFANA_USER-->/${INFLUXDB_GRAFANA_USER}/g" \ 13 | -e "s/<--GRAFANA_PW-->/${INFLUXDB_GRAFANA_PW}/g" /src/grafana/config.js 14 | 15 | touch /.grafana_configured 16 | 17 | echo "=> Grafana has been configured as follows:" 18 | echo " InfluxDB DB DATA NAME: data" 19 | echo " InfluxDB USERNAME: ${INFLUXDB_DATA_USER}" 20 | echo " InfluxDB PASSWORD: ${INFLUXDB_DATA_PW}" 21 | echo " InfluxDB DB GRAFANA NAME: grafana" 22 | echo " InfluxDB USERNAME: ${INFLUXDB_GRAFANA_USER}" 23 | echo " InfluxDB PASSWORD: ${INFLUXDB_GRAFANA_USER}" 24 | echo " ** Please check your environment variables if you find something is misconfigured. **" 25 | echo "=> Done!" 26 | exit 0 27 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/set_influxdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -m 4 | CONFIG_FILE="/etc/influxdb/config.toml" 5 | 6 | API_URL="http://localhost:8086" 7 | 8 | #if [ -n "${FORCE_HOSTNAME}" ]; then 9 | # if [ "${FORCE_HOSTNAME}" == "auto" ]; then 10 | # #set hostname with IPv4 eth0 11 | # HOSTIPNAME=$(ip a show dev eth0 | grep inet | grep eth0 | sed -e 's/^.*inet.//g' -e 's/\/.*$//g') 12 | # /usr/bin/perl -p -i -e "s/^# hostname.*$/hostname = \"${HOSTIPNAME}\"/g" ${CONFIG_FILE} 13 | # else 14 | # /usr/bin/perl -p -i -e "s/^# hostname.*$/hostname = \"${FORCE_HOSTNAME}\"/g" ${CONFIG_FILE} 15 | # fi 16 | #fi 17 | # 18 | #if [ -n "${SEEDS}" ]; then 19 | # /usr/bin/perl -p -i -e "s/^# seed-servers.*$/seed-servers = [${SEEDS}]/g" ${CONFIG_FILE} 20 | #fi 21 | # 22 | #if [ -n "${REPLI_FACTOR}" ]; then 23 | # /usr/bin/perl -p -i -e "s/replication-factor = 1/replication-factor = ${REPLI_FACTOR}/g" ${CONFIG_FILE} 24 | #fi 25 | # 26 | #if [ "${PRE_CREATE_DB}" == "**None**" ]; then 27 | # unset PRE_CREATE_DB 28 | #fi 29 | # 30 | #if [ "${SSL_CERT}" == "**None**" ]; then 31 | # unset SSL_CERT 32 | #fi 33 | # 34 | #API_URL="http://localhost:8086" 35 | #if [ -n "${SSL_CERT}" ]; then 36 | # echo "=> Found ssl cert file, using ssl api instead" 37 | # echo "=> Listening on port 8084(https api), disabling port 8086(http api)" 38 | # echo -e "${SSL_CERT}" > /cert.pem 39 | # sed -i -r -e 's/^# ssl-/ssl-/g' -e 's/^port *= * 8086/# port = 8086/' ${CONFIG_FILE} 40 | # API_URL="https://localhost:8084" 41 | #fi 42 | 43 | echo "=> About to create the following database: ${PRE_CREATE_DB}" 44 | if [ -f "/.influxdb_configured" ]; then 45 | echo "=> Database had been created before, skipping ..." 46 | else 47 | echo "=> Starting InfluxDB ..." 48 | exec /usr/bin/influxdb -config=${CONFIG_FILE} & 49 | arr=$(echo ${PRE_CREATE_DB} | tr ";" "\n") 50 | 51 | #wait for the startup of influxdb 52 | RET=1 53 | while [[ RET -ne 0 ]]; do 54 | echo "=> Waiting for confirmation of InfluxDB service startup ..." 55 | sleep 3 56 | curl -k ${API_URL}/ping 2> /dev/null 57 | RET=$? 58 | done 59 | echo "" 60 | 61 | for x in $arr 62 | do 63 | echo "=> Creating database: ${x}" 64 | curl -s -k -X POST -d "{\"name\":\"${x}\"}" $(echo ${API_URL}'/db?u=root&p=root') 65 | done 66 | echo "" 67 | 68 | echo "=> Creating User for database: data" 69 | curl -s -k -X POST -d "{\"name\":\"${INFLUXDB_DATA_USER}\",\"password\":\"${INFLUXDB_DATA_PW}\"}" $(echo ${API_URL}'/db/data/users?u=root&p=root') 70 | echo "=> Creating User for database: grafana" 71 | curl -s -k -X POST -d "{\"name\":\"${INFLUXDB_GRAFANA_USER}\",\"password\":\"${INFLUXDB_GRAFANA_PW}\"}" $(echo ${API_URL}'/db/grafana/users?u=root&p=root') 72 | echo "" 73 | 74 | echo "=> Changing Password for User: root" 75 | curl -s -k -X POST -d "{\"password\":\"${ROOT_PW}\"}" $(echo ${API_URL}'/cluster_admins/root?u=root&p=root') 76 | echo "" 77 | 78 | touch "/.influxdb_configured" 79 | exit 0 80 | fi 81 | 82 | exit 0 83 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/start_visualisation.sh: -------------------------------------------------------------------------------- 1 | docker run -d -v /etc/localtime:/etc/localtime:ro -p 80:80 -p 8083:8083 -p 8084:8084 -p 8086:8086 --name grafana-influxdb_con grafana_influxdb 2 | -------------------------------------------------------------------------------- /3rdparty/docker-grafana-influxdb/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon = true 3 | 4 | [program:grafana-server] 5 | directory = /src/grafana/ 6 | command = /src/grafana/bin/grafana-server 7 | stdout_logfile = /var/log/supervisor/%(program_name)s.log 8 | stderr_logfile = /var/log/supervisor/%(program_name)s.log 9 | autorestart = true 10 | 11 | [program:influxdb] 12 | command = /usr/local/bin/run_influxdb 13 | stdout_logfile = /var/log/supervisor/%(program_name)s.log 14 | stderr_logfile = /var/log/supervisor/%(program_name)s.log 15 | autorestart = true 16 | 17 | [program:grafanasetup] 18 | command = /grafana-influxdb-wiring.sh 19 | stdout_logfile = /var/log/supervisor/%(program_name)s.log 20 | stderr_logfile = /var/log/supervisor/%(program_name)s.log 21 | startretries=4 22 | exitcodes=0 23 | autorestart = false -------------------------------------------------------------------------------- /3rdparty/lib/curlcpp.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/3rdparty/lib/curlcpp.tar.gz -------------------------------------------------------------------------------- /3rdparty/lib/gmock-1.7.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/3rdparty/lib/gmock-1.7.0.tar.gz -------------------------------------------------------------------------------- /3rdparty/lib/pbjson.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/3rdparty/lib/pbjson.tar.gz -------------------------------------------------------------------------------- /3rdparty/lib/pwave/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /3rdparty/lib/pwave/README.md: -------------------------------------------------------------------------------- 1 | # P-Wave (pwave) 2 | 3 | ![P-Wave](https://github.com/Bplotka/pwave/blob/master/doc/pwave.png) 4 | 5 | Simple & smart header-only library for sequential signal generation. 6 | 7 | Useful for testing complicated algorithms like Moving Average calculations, Change Point Detections. 8 | 9 | Used in https://github.com/mesosphere/serenity 10 | 11 | ## Features 12 | 13 | * Generated samples based on custom math model (any f(x) like linear, sinus etc) 14 | * Optional noise modifiers (deterministic or random) 15 | * Custom modifiers (spikes, drops etc) 16 | * Print as CSV 17 | 18 | 19 | ## Usage 20 | 21 | 1. Include `pwave` in your `c++11` code. 22 | 2. Use robust `SignalScenario` class for creating custom, complex scenarios: 23 | 24 | ```cpp 25 | SignalScenario signalGen = 26 | SignalScenario(NUMBER_OF_ITERATIONS) 27 | .use(math::linearFunction) 28 | .after(12).add(-24.05) 29 | .after(2).use(new SymetricNoiseGenerator(MAX_NOISE)) 30 | .after(23).use(math::sinFunction); 31 | ``` 32 | 33 | 3. Iterate over generated values: 34 | 35 | ```cpp 36 | ITERATE_SIGNAL(signalGen) { 37 | // Use generated result in your code. 38 | double_t result = (*signalGen)(); 39 | // See result as CSV: 40 | (*signalGen).printCSVLine(result); 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /3rdparty/lib/pwave/doc/pwave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/3rdparty/lib/pwave/doc/pwave.png -------------------------------------------------------------------------------- /3rdparty/lib/pwave/noise.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PWAVE_NOISE_HPP 2 | #define PWAVE_NOISE_HPP 3 | 4 | namespace pwave { 5 | 6 | constexpr double_t DEFAULT_MAX_NOISE = 50; 7 | 8 | /** 9 | * Base class for all Noise Generators. 10 | * It is needed for Load Generator to introduce noise in samples. 11 | */ 12 | class NoiseGenerator { 13 | public: 14 | virtual double_t generate(size_t iteration) = 0; 15 | }; 16 | 17 | 18 | /** 19 | * Primary Noise Generator - generates no noise. 20 | */ 21 | class ZeroNoise : public NoiseGenerator { 22 | public: 23 | double_t generate(size_t iteration) { 24 | return 0; 25 | } 26 | }; 27 | 28 | 29 | /** 30 | * Symetric Noise Generator produces deterministic noise. 31 | * It is similar to sine wave. It alternates: raises to maxNoise 32 | * and -maxNoise, then stays low. 33 | * Average of generated values equals 0. 34 | */ 35 | class SymetricNoiseGenerator : public NoiseGenerator { 36 | public: 37 | explicit SymetricNoiseGenerator(double_t _maxNoise) : maxNoise(_maxNoise) {} 38 | 39 | double_t generate(size_t iteration) { 40 | sign *= -1; 41 | if (iteration % 2 == 0) { 42 | noise += noiseModifier; 43 | if (std::abs(noise) >= maxNoise) noiseModifier *= -1; 44 | } 45 | return (noise * sign); 46 | } 47 | 48 | double_t noiseModifier = 2; 49 | double_t maxNoise = DEFAULT_MAX_NOISE; 50 | 51 | private: 52 | int16_t sign = -1; 53 | double_t noise = 0; 54 | }; 55 | 56 | } // namespace pwave 57 | 58 | #endif // PWAVE_NOISE_HPP 59 | -------------------------------------------------------------------------------- /3rdparty/lib/pwave/tests/pwave_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "pwave/scenario.hpp" 6 | 7 | namespace pwave { 8 | 9 | TEST(PwaveTest, ScenarioExample) { 10 | const int32_t ITERATIONS = 100; 11 | 12 | SignalScenario signalGen = 13 | SignalScenario(ITERATIONS) 14 | .use(math::linearFunction) 15 | .after(12).add(-24.05) 16 | .after(2).use(new SymetricNoiseGenerator(3)) 17 | .after(23).use(math::const10Function) 18 | .after(4).constantAdd(-3.2, 10); 19 | 20 | ITERATE_SIGNAL(signalGen) { 21 | // Use generated result in your code. 22 | double_t result = (*signalGen)(); 23 | // See result as CSV: 24 | (*signalGen).printCSVLine(signalGen->cumulative()); 25 | } 26 | } 27 | 28 | } // namespace pwave 29 | -------------------------------------------------------------------------------- /3rdparty/lib/rapidjson-v1.0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/3rdparty/lib/rapidjson-v1.0.2.tar.gz -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bplotka/mesos-modules-dev:0.27.0-cmt 2 | MAINTAINER serenity 3 | 4 | ADD . /serenity 5 | 6 | WORKDIR /serenity 7 | 8 | # Check for style errors. 9 | RUN ./scripts/lint.sh 10 | 11 | # Install the picojson headers. 12 | RUN wget https://raw.githubusercontent.com/kazuho/picojson/v1.3.0/picojson.h -O /usr/local/include/picojson.h 13 | 14 | # Build serenity. 15 | # We need libmesos which is located in /usr/local/lib. 16 | RUN rm -rf build && \ 17 | mkdir build && \ 18 | cd build && \ 19 | export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/usr/local/lib && \ 20 | cmake -DWITH_MESOS="/mesos" \ 21 | -DWITH_SOURCE_MESOS="/mesos" \ 22 | -DCMT_ENABLED=ON \ 23 | -DUSE_CLANG=ON .. && \ 24 | make -j 2 && \ 25 | ./serenity-tests 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Serenity docs 2 | 3 | ## Architecture documents 4 | 5 | The freshest [Documentation](https://docs.google.com/document/d/1A9VnA-CCI4btoBvLLeYn3b0ScJrMQ-0CIiX4sTYnNhI/edit#). 6 | The original [Serenity architecture doc](https://docs.google.com/document/d/1ekr3uIGvoXrg3daJCPRyvys37teOOf1CA0sIO4jtSAo/edit#). 7 | 8 | [Developers Manual](https://github.com/mesosphere/serenity/blob/master/docs/developers.md). 9 | 10 | ## Code documentation 11 | 12 | Serenity has Doxygen code comments and can be rendered by running: 13 | 14 | ``` 15 | cd serenity 16 | doxygen 17 | ``` 18 | 19 | Docs can then be browsed from `docs/html/index.html`. 20 | 21 | ## Smoke Test Framework documentation 22 | 23 | Serenity includes additional custom framework for fast spawning 24 | numerous batch jobs. See [Smoke Test Framework Documentation](https://github.com/mesosphere/serenity/blob/master/docs/smoke_framework.md). 25 | -------------------------------------------------------------------------------- /docs/developer.md: -------------------------------------------------------------------------------- 1 | # Developer's Manual 2 | 3 | ## Serenity Components 4 | * Every component should produce all it's products in the iteration. 5 | * Components must not throw exceptions. 6 | 7 | ## Pipeline -------------------------------------------------------------------------------- /docs/images/serenity_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d2iq-archive/serenity/5311d848ba871d3cc569d5d5b731628863b8b1a0/docs/images/serenity_pipeline.png -------------------------------------------------------------------------------- /docs/smoke_framework.md: -------------------------------------------------------------------------------- 1 | ### Smoke Test Framework Guide 2 | 3 | Serenity introduces improved smoke_test_framework. 4 | It adds several features and is based on example Mesos NoExecutor framework: 5 | 6 | 1. Defining tasks scenarios using JSON files. 7 | 2. Adding URI to task. 8 | 3. Adding ability to fill all nodes resources with custom tasks. 9 | 4. Targeting task to particular host. 10 | 5. Specifying role of framework. 11 | 6. Specifying shares for each task to customize how often they should be chosen. (shares) 12 | 13 | ## Building 14 | 15 | Smoke Test Framework builds with Serenity when `-DWITH_SOURCE_MESOS=` option 16 | is specified. 17 | 18 | ## Task Specification via JSON 19 | 20 | You can run Smoke Test Framework and as the input give JSON file. 21 | Scheme: 22 | 23 | ```javascript 24 | { 25 | "tasks": [ 26 | { 27 | "command": , 28 | "uri": [Optional] { 29 | "value": ,, 30 | "executable": , 31 | "extract": , 32 | "cache": 33 | }, 34 | "taskResources": , 35 | "revocableResources": <[Optional] needed revocable resources>, 36 | "targetHostname": <[Optional] target host>, 37 | "totalTasks": <[Optional] number of tasks or if not specified - unlimited>, 38 | "shares" : <[Optional] share (priority) of the task> 39 | } 40 | ] 41 | } 42 | ``` 43 | 44 | Example (using rkt container): 45 | 46 | ```javascript 47 | { 48 | "tasks": [ 49 | { 50 | "command": "/usr/local/bin/rkt run --insecure-skip-verify --mds-register=false docker://jess/stress --exec /usr/bin/stress -- -c 1", 51 | "uri": { 52 | "value": "custom_uri", 53 | "executable": "false", 54 | "extract": "true", 55 | "cache": "false" 56 | }, 57 | "taskResources": "mem(serenity):64", 58 | "revocableResources": "cpus(serenity):1", 59 | "targetHostname": "my_super_agent@serenity.com", 60 | "totalTasks": 5, 61 | "shares" : 99 62 | } 63 | ] 64 | } 65 | ``` 66 | 67 | ## Usage 68 | 69 | You can easily run framework using CLI: 70 | 71 | Using JSON file (recommended): 72 | 73 | `./test-framework --role= --logging_level=INFO --json_path="${JSON_PATH}"` 74 | 75 | Using Args: 76 | 77 | `./test-framework --role= --logging_level=INFO --task_resources=<> 78 | --task_revocable_resources=<> --num_tasks=<> --target_hostname=<> --uri_value --command=<>` 79 | -------------------------------------------------------------------------------- /scripts/graphana_run.sh: -------------------------------------------------------------------------------- 1 | docker run -p 3000:3000 grafana/grafana 2 | -------------------------------------------------------------------------------- /scripts/influxdb_run.sh: -------------------------------------------------------------------------------- 1 | docker run -d -p 8083:8083 -p 8086:8086 --expose 8083 --expose 8086 tutum/influxdb:0.9 2 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 4 | SERENITY_ROOT=$( dirname $(readlink -e "${DIR}/")) 5 | 6 | # Run lint on all src/* cpp/hpp files except smoke_test_framework source yet. 7 | python ${DIR}/cpplint.py \ 8 | --extensions="hpp,cpp" \ 9 | --filter="-legal/copyright" \ 10 | $( find "${SERENITY_ROOT}/" -name "*.cpp" -or -name "*.hpp" | \ 11 | grep -e "${SERENITY_ROOT}/src/" | \ 12 | grep -v -e "${SERENITY_ROOT}/src/mesos_frameworks/smoke_test/") 13 | -------------------------------------------------------------------------------- /scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | exec ./scripts/lint.sh 25 | if [ $? != 0 ] 26 | then 27 | echo "Failed to commit: style errors" 28 | exit 1 29 | fi 30 | 31 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 32 | # them from being added to the repository. We exploit the fact that the 33 | # printable range starts at the space character and ends with tilde. 34 | if [ "$allownonascii" != "true" ] && 35 | # Note that the use of brackets around a tr range is ok here, (it's 36 | # even required, for portability to Solaris 10's /usr/bin/tr), since 37 | # the square bracket bytes happen to fall in the designated range. 38 | test $(git diff --cached --name-only --diff-filter=A -z $against | 39 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 40 | then 41 | cat <<\EOF 42 | Error: Attempt to add a non-ASCII file name. 43 | 44 | This can cause problems if you want to work with people on other platforms. 45 | 46 | To be portable it is advisable to rename the file. 47 | 48 | If you know what you are doing you can disable this check using: 49 | 50 | git config hooks.allownonascii true 51 | EOF 52 | exit 1 53 | fi 54 | 55 | # If there are whitespace errors, print the offending file names and fail. 56 | exec git diff-index --check --cached $against -- 57 | -------------------------------------------------------------------------------- /src/bus/event_bus.cpp: -------------------------------------------------------------------------------- 1 | #include "bus/event_bus.hpp" 2 | 3 | namespace mesos { 4 | namespace serenity { 5 | 6 | std::once_flag StaticEventBus::onlyOneEventBusInit; 7 | std::unique_ptr StaticEventBus::eventBus = nullptr; 8 | 9 | } // namespace serenity 10 | } // namespace mesos 11 | -------------------------------------------------------------------------------- /src/contention_detectors/overload.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "contention_detectors/overload.hpp" 5 | 6 | #include "mesos/resources.hpp" 7 | 8 | namespace mesos { 9 | namespace serenity { 10 | 11 | Try OverloadDetector::consume(const ResourceUsage& in) { 12 | Contentions product; 13 | 14 | if (in.total_size() == 0) { 15 | return Error(std::string(NAME) + " No total in ResourceUsage"); 16 | } 17 | 18 | Resources totalAgentResources(in.total()); 19 | Option totalAgentCpus = totalAgentResources.cpus(); 20 | 21 | if (totalAgentCpus.isNone()) { 22 | return Error(std::string(NAME) + " No total cpus in ResourceUsage"); 23 | } 24 | 25 | double_t thresholdCpus = this->cfgUtilizationThreshold * totalAgentCpus.get(); 26 | double_t agentSumCpus = 0; 27 | uint64_t beExecutors = 0; 28 | 29 | for (const ResourceUsage_Executor& inExec : in.executors()) { 30 | if (!inExec.has_executor_info()) { 31 | SERENITY_LOG(ERROR) << "Executor " 32 | << " does not include executor_info"; 33 | // Filter out these executors. 34 | continue; 35 | } 36 | if (!inExec.has_statistics()) { 37 | SERENITY_LOG(ERROR) << "Executor " 38 | << inExec.executor_info().executor_id().value() 39 | << " does not include statistics."; 40 | // Filter out these executors. 41 | continue; 42 | } 43 | 44 | Try value = this->cpuUsageGetFunction(inExec); 45 | if (value.isError()) { 46 | SERENITY_LOG(ERROR) << value.error(); 47 | continue; 48 | } 49 | 50 | agentSumCpus += value.get(); 51 | 52 | if (!Resources(inExec.allocated()).revocable().empty()) { 53 | beExecutors++; 54 | } 55 | } 56 | 57 | SERENITY_LOG(INFO) << "Sum = " << agentSumCpus << " vs total = " 58 | << totalAgentCpus.get() << " [threshold = " << thresholdCpus << "]"; 59 | 60 | if (agentSumCpus > thresholdCpus) { 61 | if (beExecutors == 0) { 62 | SERENITY_LOG(INFO) << "No BE tasks - only high host utilization"; 63 | } else { 64 | // Severity is the amount of the CPUs above the threshold. 65 | double_t severity = agentSumCpus - thresholdCpus; 66 | SERENITY_LOG(INFO) << "Creating CPU contention, because of the " 67 | << severity << " CPUs above the threshold. "; 68 | 69 | product.push_back(createContention(severity, Contention_Type_CPU)); 70 | } 71 | } 72 | 73 | // Continue pipeline. 74 | this->produce(product); 75 | 76 | return Nothing(); 77 | } 78 | 79 | 80 | } // namespace serenity 81 | } // namespace mesos 82 | -------------------------------------------------------------------------------- /src/contention_detectors/overload.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_OVERLOAD_DETECTOR_HPP 2 | #define SERENITY_OVERLOAD_DETECTOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "glog/logging.h" 8 | 9 | #include "messages/serenity.hpp" 10 | 11 | #include "serenity/config.hpp" 12 | #include "serenity/data_utils.hpp" 13 | #include "serenity/serenity.hpp" 14 | #include "serenity/wid.hpp" 15 | 16 | #include "stout/lambda.hpp" 17 | #include "stout/nothing.hpp" 18 | #include "stout/option.hpp" 19 | 20 | namespace mesos { 21 | namespace serenity { 22 | 23 | class OverloadDetectorConfig : public SerenityConfig { 24 | public: 25 | OverloadDetectorConfig() { } 26 | 27 | explicit OverloadDetectorConfig(const SerenityConfig& customCfg) { 28 | this->initDefaults(); 29 | this->applyConfig(customCfg); 30 | } 31 | 32 | void initDefaults() { 33 | //! double_t 34 | //! Detector threshold. 35 | this->fields[detector::THRESHOLD] = 36 | detector::DEFAULT_UTILIZATION_THRESHOLD; 37 | } 38 | }; 39 | 40 | /** 41 | * OverloadDetector is able to create contention if utilization is above 42 | * given thresholds. 43 | */ 44 | class OverloadDetector : 45 | public Consumer, 46 | public Producer { 47 | public: 48 | OverloadDetector( 49 | Consumer* _consumer, 50 | const lambda::function& _cpuUsageGetFunction, 51 | SerenityConfig _conf, 52 | const Tag& _tag = Tag(QOS_CONTROLLER, NAME)) 53 | : tag(_tag), 54 | cpuUsageGetFunction(_cpuUsageGetFunction), 55 | Producer(_consumer) { 56 | SerenityConfig config = OverloadDetectorConfig(_conf); 57 | this->cfgUtilizationThreshold = 58 | config.getD(detector::THRESHOLD); 59 | } 60 | 61 | ~OverloadDetector() {} 62 | 63 | Try consume(const ResourceUsage& in) override; 64 | 65 | static const constexpr char* NAME = "OverloadDetector"; 66 | 67 | protected: 68 | const Tag tag; 69 | const lambda::function cpuUsageGetFunction; 70 | 71 | // cfg parameters. 72 | double_t cfgUtilizationThreshold; 73 | }; 74 | 75 | } // namespace serenity 76 | } // namespace mesos 77 | 78 | #endif // SERENITY_OVERLOAD_DETECTOR_HPP 79 | -------------------------------------------------------------------------------- /src/contention_detectors/signal_analyzers/base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_SIGNAL_ANALYZER_HPP 2 | #define SERENITY_SIGNAL_ANALYZER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "serenity/serenity.hpp" 8 | 9 | #include "stout/nothing.hpp" 10 | #include "stout/try.hpp" 11 | #include "stout/result.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | struct Detection { 17 | Detection() : severity(None()) {} 18 | 19 | Option severity; 20 | }; 21 | 22 | /** 23 | * Sequential signal analyzer interface. 24 | * It can receive and process observations sequentially over time. 25 | */ 26 | class SignalAnalyzer { 27 | public: 28 | explicit SignalAnalyzer(const Tag& _tag) : tag(_tag) { 29 | } 30 | 31 | virtual Result processSample(double_t in) = 0; 32 | 33 | virtual Try resetSignalRecovering() = 0; 34 | 35 | protected: 36 | const Tag tag; 37 | 38 | /** 39 | * Contention Factory. 40 | */ 41 | Detection createContention(double_t severity) { 42 | Detection cpd; 43 | if (severity > 0) { 44 | cpd.severity = severity; 45 | } 46 | 47 | SERENITY_LOG(INFO) << " Created contention with severity = " 48 | << (cpd.severity.isSome() ? std::to_string(cpd.severity.get()) : ""); 49 | return cpd; 50 | } 51 | }; 52 | 53 | 54 | } // namespace serenity 55 | } // namespace mesos 56 | 57 | #endif // SERENITY_SIGNAL_ANALYZER_HPP 58 | -------------------------------------------------------------------------------- /src/contention_detectors/signal_based.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "contention_detectors/signal_based.hpp" 5 | 6 | namespace mesos { 7 | namespace serenity { 8 | 9 | Try SignalBasedDetector::consume(const ResourceUsage& usage) { 10 | auto executorsListsTuple = 11 | ResourceUsageHelper::getProductionAndRevocableExecutors(usage); 12 | 13 | std::list productionExecutors = 14 | std::get( 15 | executorsListsTuple); 16 | std::list revocableExecutors = 17 | std::get( 18 | executorsListsTuple); 19 | 20 | SERENITY_LOG(INFO) << "Production executors: " << productionExecutors.size() 21 | << " | Revocable executors: " << revocableExecutors.size(); 22 | 23 | Contentions product; 24 | for (const ResourceUsage_Executor& executor : productionExecutors) { 25 | if (!ResourceUsageHelper::isExecutorHasStatistics(executor)) { 26 | SERENITY_LOG(INFO) << "No statistics for executor " 27 | << executor.executor_info().executor_id(); 28 | continue; 29 | } 30 | 31 | // Check if change point Detector for given executor exists. 32 | auto cpDetector = this->detectors.find(executor.executor_info()); 33 | if (cpDetector == this->detectors.end()) { 34 | SERENITY_LOG(INFO) << "Not found executor: " 35 | << executor.executor_info().executor_id(); 36 | this->detectors.insert( 37 | std::pair>( 38 | executor.executor_info(), 39 | std::unique_ptr( 40 | new SignalDropAnalyzer(tag, this->detectorConf)))); 41 | 42 | } else { 43 | // Check if previousSample for given executor exists. 44 | // Get proper value. 45 | Try value = this->getValue(executor); 46 | if (value.isError()) { 47 | SERENITY_LOG(ERROR) << value.error(); 48 | continue; 49 | } 50 | 51 | SERENITY_LOG(INFO) << "Starting processing executor: " 52 | << executor.executor_info().executor_id(); 53 | // Perform change point detection. 54 | Result cpDetected = 55 | (cpDetector->second)->processSample(value.get()); 56 | if (cpDetected.isError()) { 57 | SERENITY_LOG(ERROR) << cpDetected.error(); 58 | continue; 59 | } 60 | 61 | // Detected contention. 62 | if (cpDetected.isSome()) { 63 | if (revocableExecutors.empty()) { 64 | SERENITY_LOG(INFO) << "Contention spotted, however there are no " 65 | << "Best effort tasks on the host. Assuming false positive"; 66 | (cpDetector->second)->resetSignalRecovering(); 67 | } else { 68 | SERENITY_LOG(INFO) << "Signal contention spotted"; 69 | product.push_back(createContention( 70 | cpDetected.get().severity, 71 | contentionType, 72 | WID(executor.executor_info()).getWorkID(), 73 | executor.statistics().timestamp())); 74 | } 75 | } 76 | } 77 | } 78 | SERENITY_LOG(INFO) << "Producing " << product.size() << " contentions"; 79 | produce(product); 80 | return Nothing(); 81 | } 82 | 83 | } // namespace serenity 84 | } // namespace mesos 85 | -------------------------------------------------------------------------------- /src/contention_detectors/signal_based.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_SIGNAL_BASED_DETECTOR_FILTER_HPP 2 | #define SERENITY_SIGNAL_BASED_DETECTOR_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "glog/logging.h" 10 | 11 | #include "contention_detectors/signal_analyzers/drop.hpp" 12 | #include "contention_detectors/signal_analyzers/base.hpp" 13 | 14 | #include "messages/serenity.hpp" 15 | 16 | #include "serenity/config.hpp" 17 | #include "serenity/data_utils.hpp" 18 | #include "serenity/executor_map.hpp" 19 | #include "serenity/executor_set.hpp" 20 | #include "serenity/serenity.hpp" 21 | #include "serenity/resource_helper.hpp" 22 | #include "serenity/wid.hpp" 23 | 24 | #include "stout/lambda.hpp" 25 | #include "stout/nothing.hpp" 26 | #include "stout/option.hpp" 27 | #include "stout/result.hpp" 28 | 29 | namespace mesos { 30 | namespace serenity { 31 | 32 | /** 33 | * TODO(skonefal): Prepare proper docstring. 34 | * SignalBasedDetector looks at specific metric of each production executor 35 | * and emits contention when it's signal drops bellow certain percent of 36 | * previous value. 37 | */ 38 | class SignalBasedDetector : 39 | public Consumer, 40 | public Producer { 41 | public: 42 | SignalBasedDetector( 43 | Consumer* _consumer, 44 | const lambda::function& _getValue, 45 | SerenityConfig _detectorConf, 46 | const Tag& _tag = Tag(QOS_CONTROLLER, "SignalBasedDetector"), 47 | const Contention_Type _contentionType = Contention_Type_IPC) 48 | : tag(_tag), 49 | Producer(_consumer), 50 | detectors(ExecutorMap>()), 51 | getValue(_getValue), 52 | detectorConf(_detectorConf), 53 | contentionType(_contentionType) {} 54 | 55 | ~SignalBasedDetector() {} 56 | 57 | Try consume(const ResourceUsage& usage) override; 58 | 59 | static const constexpr char* NAME = "SignalBasedDetector"; 60 | 61 | protected: 62 | const Tag tag; 63 | const Contention_Type contentionType; 64 | const lambda::function getValue; 65 | 66 | // Detections. 67 | ExecutorMap> detectors; 68 | SerenityConfig detectorConf; 69 | }; 70 | 71 | } // namespace serenity 72 | } // namespace mesos 73 | 74 | #endif // SERENITY_SIGNAL_BASED_DETECTOR_FILTER_HPP 75 | -------------------------------------------------------------------------------- /src/filters/correction_merger.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_CORRECTION_MERGER_FILTER_HPP 2 | #define SERENITY_CORRECTION_MERGER_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mesos/mesos.hpp" 12 | 13 | #include "messages/serenity.hpp" 14 | 15 | #include "serenity/serenity.hpp" 16 | 17 | namespace mesos { 18 | namespace serenity { 19 | 20 | /** 21 | * Merges several observer's corrections into one. Checks for duplicates. 22 | */ 23 | class CorrectionMergerFilter: 24 | public Consumer, public Producer { 25 | public: 26 | explicit CorrectionMergerFilter( 27 | Consumer* _consumer, 28 | const Tag& _tag = Tag(QOS_CONTROLLER, NAME)) 29 | : Producer(_consumer), 30 | tag(_tag) {} 31 | 32 | ~CorrectionMergerFilter() {} 33 | 34 | virtual void allProductsReady() { 35 | std::vector qosCorrectionsVector = 36 | Consumer::getConsumables(); 37 | QoSCorrections corrections; 38 | 39 | uint64_t receivedCententionNum = 0; 40 | for (QoSCorrections product : qosCorrectionsVector) { 41 | receivedCententionNum += product.size(); 42 | for (slave::QoSCorrection correction : product) { 43 | if (checkForDuplicates(correction, corrections)) { 44 | // Filter out duplicated value. 45 | continue; 46 | } 47 | corrections.push_back(correction); 48 | } 49 | } 50 | 51 | SERENITY_LOG(INFO) << "Received " << corrections.size() << " corrections"; 52 | produce(corrections); 53 | return; 54 | } 55 | 56 | private: 57 | const Tag tag; 58 | 59 | static const constexpr char* NAME = "CorrectionMerger"; 60 | 61 | // Returns True when value is duplicated in list. 62 | // TODO(bplotka): Move to QoSCorrections std::set in future. 63 | bool checkForDuplicates( 64 | slave::QoSCorrection value, QoSCorrections corrections) { 65 | for (slave::QoSCorrection correction : corrections) { 66 | if (value.type() == slave::QoSCorrection_Type_KILL && 67 | value.has_kill() && 68 | value.kill().has_executor_id() && 69 | value.kill().has_framework_id() && 70 | correction.type() == slave::QoSCorrection_Type_KILL && 71 | correction.has_kill() && 72 | correction.kill().has_executor_id() && 73 | correction.kill().has_framework_id()) { 74 | if (correction.kill().executor_id().value() == 75 | value.kill().executor_id().value() && 76 | correction.kill().framework_id().value() == 77 | value.kill().framework_id().value()) { 78 | // Found duplicate. 79 | return true; 80 | } 81 | } else { 82 | SERENITY_LOG(WARNING) 83 | << "Received correction without all required data."; 84 | } 85 | } 86 | return false; 87 | } 88 | }; 89 | 90 | } // namespace serenity 91 | } // namespace mesos 92 | 93 | #endif // SERENITY_CORRECTION_MERGER_FILTER_HPP 94 | -------------------------------------------------------------------------------- /src/filters/cumulative.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_CUMULATIVE_FILTER_HPP 2 | #define SERENITY_CUMULATIVE_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mesos/mesos.hpp" 10 | 11 | #include "serenity/serenity.hpp" 12 | #include "serenity/executor_set.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | class CumulativeFilter : 18 | public Consumer, public Producer { 19 | public: 20 | explicit CumulativeFilter( 21 | Consumer* _consumer, 22 | const Tag& _tag = Tag(UNDEFINED, "CumulativeFilter")) 23 | : Producer(_consumer), 24 | previousSamples(new ExecutorSet()), 25 | tag(_tag) {} 26 | 27 | ~CumulativeFilter(); 28 | 29 | Try consume(const ResourceUsage& in); 30 | 31 | protected: 32 | const Tag tag; 33 | std::unique_ptr previousSamples; 34 | }; 35 | 36 | } // namespace serenity 37 | } // namespace mesos 38 | 39 | #endif // SERENITY_CUMULATIVE_FILTER_HPP 40 | -------------------------------------------------------------------------------- /src/filters/executor_age.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "glog/logging.h" 6 | 7 | #include "mesos/mesos.hpp" 8 | 9 | #include "serenity/executor_map.hpp" 10 | 11 | #include "executor_age.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | using std::map; 17 | using std::pair; 18 | using std::string; 19 | 20 | ExecutorAgeFilter::ExecutorAgeFilter() : started(new ExecutorMap()) {} 21 | 22 | 23 | ExecutorAgeFilter::ExecutorAgeFilter(Consumer* _consumer) 24 | : Producer(_consumer), started(new ExecutorMap()) {} 25 | 26 | 27 | ExecutorAgeFilter::~ExecutorAgeFilter() {} 28 | 29 | 30 | Try ExecutorAgeFilter::consume(const ResourceUsage& in) { 31 | double_t now = time(NULL); 32 | 33 | for (ResourceUsage_Executor executor : in.executors()) { 34 | auto startedTime = this->started->find(executor.executor_info()); 35 | if (startedTime == this->started->end()) { 36 | // If executor is missing, create start entry for executor. 37 | this->started->insert(pair( 38 | executor.executor_info(), now)); 39 | this->age(executor.executor_info()); // For test! 40 | } 41 | } 42 | // TODO(nnielsen): Clean up finished frameworks and executors. 43 | 44 | this->produce(in); 45 | return Nothing(); 46 | } 47 | 48 | 49 | Try ExecutorAgeFilter::age(const ExecutorInfo& executorInfo) { 50 | auto startedTime = started->find(executorInfo); 51 | if (startedTime == started->end()) { 52 | return Error( 53 | "Could not find started time for executor '" + 54 | executorInfo.framework_id().value() + "' of framework '" + 55 | executorInfo.executor_id().value() + "': framework not present"); 56 | } else { 57 | return difftime(time(NULL), startedTime->second); 58 | } 59 | } 60 | 61 | } // namespace serenity 62 | } // namespace mesos 63 | -------------------------------------------------------------------------------- /src/filters/executor_age.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_EXECUTOR_AGE_FILTER_HPP 2 | #define SERENITY_EXECUTOR_AGE_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mesos/mesos.hpp" 10 | 11 | #include "serenity/executor_map.hpp" 12 | #include "serenity/serenity.hpp" 13 | 14 | 15 | namespace mesos { 16 | namespace serenity { 17 | 18 | class ExecutorAgeFilter : 19 | public Consumer, public Producer { 20 | public: 21 | ExecutorAgeFilter(); 22 | 23 | explicit ExecutorAgeFilter(Consumer* _consumer); 24 | 25 | ~ExecutorAgeFilter(); 26 | 27 | Try consume(const ResourceUsage& in); 28 | 29 | /** 30 | * Returns the age of an executor in seconds. 31 | */ 32 | Try age(const ExecutorInfo& exec_id); 33 | 34 | private: 35 | std::unique_ptr> started; 36 | }; 37 | 38 | } // namespace serenity 39 | } // namespace mesos 40 | 41 | #endif // SERENITY_EXECUTOR_AGE_FILTER_HPP 42 | -------------------------------------------------------------------------------- /src/filters/ignore_new_executors.cpp: -------------------------------------------------------------------------------- 1 | #include "filters/ignore_new_executors.hpp" 2 | 3 | #include "glog/logging.h" 4 | 5 | namespace mesos { 6 | namespace serenity { 7 | 8 | Try IgnoreNewExecutorsFilter::consume(const ResourceUsage &usage) { 9 | std::unique_ptr> newExecutorTimestamps = 10 | std::unique_ptr>(new ExecutorMap()); 11 | ResourceUsage product; 12 | 13 | time_t timeNow = this->GetTime(nullptr); 14 | // insert method result: tuple 15 | auto resultPair = std::make_pair(executorTimestamps->begin() , true); 16 | for (const auto& executor : usage.executors()) { 17 | if (!executor.has_executor_info()) { 18 | LOG(ERROR) << name << "Executor " 19 | << " does not include executor_info"; 20 | // Filter out these executors. 21 | continue; 22 | } 23 | 24 | if (!executor.has_statistics()) { 25 | LOG(ERROR) << name << "Executor " 26 | << executor.executor_info().executor_id().value() 27 | << " does not include statistics."; 28 | // Filter out these executors. 29 | continue; 30 | } 31 | 32 | if (!executor.statistics().has_timestamp()) { 33 | LOG(ERROR) << name << "Executor " 34 | << executor.executor_info().executor_id().value() 35 | << " does not include timestamp in statistics."; 36 | // Filter out these executors. 37 | continue; 38 | } 39 | 40 | ExecutorInfo executorInfo = executor.executor_info(); 41 | 42 | // Find executor or add it if non-existent. 43 | const auto& prevExecutorEntry = 44 | this->executorTimestamps->find(executorInfo); 45 | if (prevExecutorEntry == this->executorTimestamps->end()) { 46 | resultPair = newExecutorTimestamps->insert( 47 | std::make_pair(executor.executor_info(), 48 | executor.statistics().timestamp())); 49 | } else { 50 | resultPair = newExecutorTimestamps->insert(std::make_pair( 51 | prevExecutorEntry->first, 52 | prevExecutorEntry->second)); 53 | } 54 | 55 | // Check if insertion was successful 56 | if (resultPair.second == true) { 57 | time_t insertionTime = resultPair.first->second; 58 | // Check if insertion time is above threshold 59 | if (timeNow - insertionTime >= this->threshold) { 60 | ResourceUsage_Executor* newExec = product.mutable_executors()->Add(); 61 | newExec->CopyFrom(executor); 62 | } 63 | } else { 64 | LOG(ERROR) << name << "IgnoreNewTasksFilter: " 65 | << "Insert inside executor database failed."; 66 | } 67 | } 68 | 69 | this->executorTimestamps->clear(); 70 | this->executorTimestamps = std::move(newExecutorTimestamps); 71 | 72 | if (0 != product.executors_size()) { 73 | // Continue pipeline. 74 | // Copy total agent's capacity. 75 | product.mutable_total()->CopyFrom(usage.total()); 76 | produce(product); 77 | } 78 | 79 | return Nothing(); 80 | } 81 | 82 | } // namespace serenity 83 | } // namespace mesos 84 | -------------------------------------------------------------------------------- /src/filters/ignore_new_executors.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_IGNORE_NEW_TASKS_FILTER_HPP 2 | #define SERENITY_IGNORE_NEW_TASKS_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mesos/mesos.hpp" 9 | 10 | #include "messages/serenity.hpp" 11 | 12 | #include "serenity/default_vars.hpp" 13 | #include "serenity/executor_map.hpp" 14 | #include "serenity/serenity.hpp" 15 | 16 | #include "stout/nothing.hpp" 17 | #include "stout/option.hpp" 18 | #include "stout/try.hpp" 19 | 20 | namespace mesos { 21 | namespace serenity { 22 | 23 | 24 | /** 25 | * IgnoreNewExecutorsFilter removes executors from ResourceUsage collection 26 | * that run for less time than threshold (expressed in seconds). 27 | * 28 | * It's purpose is to cut away tasks that are warming up. 29 | */ 30 | class IgnoreNewExecutorsFilter : public Consumer, 31 | public Producer { 32 | public: 33 | explicit IgnoreNewExecutorsFilter( 34 | Consumer* _consumer = nullptr, 35 | uint32_t _thresholdSeconds = new_executor::DEFAULT_THRESHOLD_SEC) : 36 | Producer(_consumer), 37 | threshold(_thresholdSeconds), 38 | executorTimestamps(new ExecutorMap) {} 39 | 40 | ~IgnoreNewExecutorsFilter() {} 41 | 42 | IgnoreNewExecutorsFilter(const IgnoreNewExecutorsFilter& other) : 43 | threshold(other.threshold) {} 44 | 45 | Try consume(const ResourceUsage& usage) override; 46 | 47 | /// Set #seconds when executor is considered too fresh. 48 | void setThreshold(uint32_t _threshold) { 49 | this->threshold = _threshold; 50 | } 51 | 52 | protected: 53 | /// ctime function wrapped for mocking purposes. 54 | inline virtual time_t GetTime(time_t* arg) { 55 | return time(arg); 56 | } 57 | 58 | uint32_t threshold; //!< #seconds when executor is considered too fresh. 59 | 60 | std::unique_ptr> executorTimestamps; 61 | 62 | static constexpr const char* name = 63 | "[SerenityEstimator] IgnoreNewExecutorsFilter: "; 64 | }; 65 | 66 | } // namespace serenity 67 | } // namespace mesos 68 | 69 | #endif // SERENITY_IGNORE_NEW_TASKS_FILTER_HPP 70 | -------------------------------------------------------------------------------- /src/filters/pr_executor_pass.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "filters/pr_executor_pass.hpp" 4 | 5 | namespace mesos { 6 | namespace serenity { 7 | 8 | Try PrExecutorPassFilter::consume(const ResourceUsage& in) { 9 | ResourceUsage product; 10 | product.mutable_total()->CopyFrom(in.total()); 11 | for (ResourceUsage_Executor inExec : in.executors()) { 12 | if (!inExec.has_executor_info()) { 13 | LOG(ERROR) << name << "Executor " 14 | << " does not include executor_info"; 15 | // Filter out these executors. 16 | continue; 17 | } 18 | if (inExec.allocated().size() == 0) { 19 | LOG(ERROR) << name << "Executor " 20 | << inExec.executor_info().executor_id().value() 21 | << " does not include allocated resources."; 22 | // Filter out these executors. 23 | continue; 24 | } 25 | 26 | Resources allocated(inExec.allocated()); 27 | // Check if task uses revocable resources. 28 | if (!allocated.revocable().empty()) { 29 | continue; 30 | } 31 | 32 | // Add an PR executor. 33 | ResourceUsage_Executor* outExec = product.mutable_executors()->Add(); 34 | outExec->CopyFrom(inExec); 35 | } 36 | 37 | produce(product); 38 | 39 | return Nothing(); 40 | } 41 | 42 | } // namespace serenity 43 | } // namespace mesos 44 | -------------------------------------------------------------------------------- /src/filters/pr_executor_pass.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_PR_EXECUTOR_PASS_FILTER_HPP 2 | #define SERENITY_PR_EXECUTOR_PASS_FILTER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "mesos/resources.hpp" 8 | 9 | #include "messages/serenity.hpp" 10 | 11 | #include "serenity/serenity.hpp" 12 | 13 | #include "stout/lambda.hpp" 14 | #include "stout/nothing.hpp" 15 | 16 | namespace mesos { 17 | namespace serenity { 18 | 19 | /** 20 | * Filter retaining ResourceUsage for production executors only. 21 | */ 22 | class PrExecutorPassFilter : 23 | public Consumer, 24 | public Producer { 25 | public: 26 | explicit PrExecutorPassFilter(Consumer* _consumer) 27 | : Producer(_consumer) {} 28 | 29 | ~PrExecutorPassFilter() {} 30 | 31 | Try consume(const ResourceUsage& in); 32 | 33 | static constexpr const char* name = "[Serenity] PrExecutorPasFilter: "; 34 | }; 35 | 36 | } // namespace serenity 37 | } // namespace mesos 38 | 39 | #endif // SERENITY_PR_TASKS_FILTER_HPP 40 | -------------------------------------------------------------------------------- /src/filters/too_low_usage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "glog/logging.h" 6 | 7 | #include "mesos/mesos.hpp" 8 | #include "mesos/resources.hpp" 9 | 10 | #include "filters/too_low_usage.hpp" 11 | 12 | #include "serenity/data_utils.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | using std::map; 18 | using std::pair; 19 | using std::string; 20 | 21 | TooLowUsageFilter::~TooLowUsageFilter() {} 22 | 23 | 24 | Try TooLowUsageFilter::consume(const ResourceUsage& in) { 25 | ResourceUsage product; 26 | product.mutable_total()->CopyFrom(in.total()); 27 | 28 | for (const ResourceUsage_Executor& inExec : in.executors()) { 29 | string executor_id = ""; 30 | 31 | if (!inExec.has_executor_info()) { 32 | SERENITY_LOG(ERROR) << "Executor " << executor_id 33 | << " does not include executor_info. "; 34 | // Filter out these executors. 35 | continue; 36 | } else { 37 | executor_id = inExec.executor_info().executor_id().value(); 38 | } 39 | 40 | if (!inExec.has_statistics()) { 41 | SERENITY_LOG(ERROR) << "Executor " << executor_id 42 | << " does not include statistics. "; 43 | // Filter out these executors. 44 | continue; 45 | } 46 | 47 | Resources allocated(inExec.allocated()); 48 | // Check if task uses revocable resources. 49 | if (allocated.revocable().empty()) { 50 | // Consider this executor as PR. 51 | // Check if CPU Usage is not too low. 52 | // (Signal is jitter when CPU is too low) 53 | // NOTE(bplotka): We pick non-ema CPU Usage here to have the freshest 54 | // data. 55 | Try cpuUsage = usage::getCpuUsage(inExec); 56 | if (cpuUsage.isError()) { 57 | SERENITY_LOG(ERROR) << cpuUsage.error(); 58 | continue; 59 | } 60 | 61 | if (cpuUsage.get() <= this->cfgMinimalCpuUsage) { 62 | // Exclude executor. 63 | SERENITY_LOG(INFO) << "Filtering out PR exec: " << executor_id 64 | << " because of its CPU Usage: " << cpuUsage.get() << " [Min: " 65 | << this->cfgMinimalCpuUsage << "]"; 66 | continue; 67 | } 68 | } 69 | 70 | // Add not excluded executor. 71 | ResourceUsage_Executor* outExec = product.mutable_executors()->Add(); 72 | outExec->CopyFrom(inExec); 73 | } 74 | 75 | // Continue pipeline. 76 | produce(product); 77 | 78 | return Nothing(); 79 | } 80 | 81 | } // namespace serenity 82 | } // namespace mesos 83 | -------------------------------------------------------------------------------- /src/filters/too_low_usage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_TOO_LOW_USAGE_FILTER_HPP 2 | #define SERENITY_TOO_LOW_USAGE_FILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mesos/mesos.hpp" 10 | 11 | #include "serenity/config.hpp" 12 | #include "serenity/serenity.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | class TooLowUsageFilterConfig : public SerenityConfig { 18 | public: 19 | TooLowUsageFilterConfig() { } 20 | 21 | explicit TooLowUsageFilterConfig(const SerenityConfig& customCfg) { 22 | this->initDefaults(); 23 | this->applyConfig(customCfg); 24 | } 25 | 26 | void initDefaults() { 27 | //! double_t 28 | //! Minimal cpu usage 29 | this->fields[too_low_usage::MINIMAL_CPU_USAGE] = 30 | too_low_usage::DEFAULT_MINIMAL_CPU_USAGE; 31 | } 32 | }; 33 | 34 | 35 | /** 36 | * Filter out PR executors with too low metrics. 37 | * Currently we filter out when CPU Usage is below specified threshold. 38 | */ 39 | class TooLowUsageFilter : 40 | public Consumer, public Producer { 41 | public: 42 | explicit TooLowUsageFilter(const Tag& _tag = Tag(QOS_CONTROLLER, NAME)) 43 | : tag(_tag) {} 44 | 45 | explicit TooLowUsageFilter( 46 | Consumer* _consumer, 47 | SerenityConfig _conf, 48 | const Tag& _tag = Tag(QOS_CONTROLLER, NAME)) 49 | : Producer(_consumer), tag(_tag) { 50 | SerenityConfig config = TooLowUsageFilterConfig(_conf); 51 | this->cfgMinimalCpuUsage = config.getD(too_low_usage::MINIMAL_CPU_USAGE); 52 | } 53 | 54 | ~TooLowUsageFilter(); 55 | 56 | static const constexpr char* NAME = "TooLowUsageFilter"; 57 | 58 | Try consume(const ResourceUsage& in); 59 | 60 | public: 61 | const Tag tag; 62 | 63 | double_t cfgMinimalCpuUsage; 64 | }; 65 | 66 | } // namespace serenity 67 | } // namespace mesos 68 | 69 | #endif // SERENITY_TOO_LOW_USAGE_FILTER_HPP 70 | -------------------------------------------------------------------------------- /src/filters/utilization_threshold.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_UTILIZATION_THRESHOLD_FILTER_HPP 2 | #define SERENITY_UTILIZATION_THRESHOLD_FILTER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "serenity/default_vars.hpp" 8 | #include "serenity/executor_set.hpp" 9 | #include "serenity/serenity.hpp" 10 | 11 | #include "stout/lambda.hpp" 12 | #include "stout/nothing.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | /** 18 | * UtilizationThresholdFilter disables oversubscription when node 19 | * utilization is too high. 20 | * NOTE: This filter should be the first filter in pipeline. It should have 21 | * the overview of all tasks running on the node. 22 | * NOTE: In case of lack of the usage for given executor, 23 | * filter assumes that executor uses maximum of allowed 24 | * resource (allocated) and logs warning. 25 | */ 26 | class UtilizationThresholdFilter : 27 | public Consumer, public Producer { 28 | public: 29 | UtilizationThresholdFilter( 30 | double_t _utilizationThreshold = utilization::DEFAULT_THRESHOLD, 31 | const Tag& _tag = Tag(UNDEFINED, "utilizationFilter")) 32 | : tag(_tag), 33 | utilizationThreshold(_utilizationThreshold), 34 | previousSamples(new ExecutorSet) {} 35 | 36 | UtilizationThresholdFilter( 37 | Consumer* _consumer, 38 | double_t _utilizationThreshold = utilization::DEFAULT_THRESHOLD, 39 | const Tag& _tag = Tag(UNDEFINED, "utilizationFilter")) 40 | : tag(_tag), Producer(_consumer), 41 | utilizationThreshold(_utilizationThreshold), 42 | previousSamples(new ExecutorSet) {} 43 | 44 | ~UtilizationThresholdFilter() {} 45 | 46 | Try consume(const ResourceUsage& in); 47 | 48 | protected: 49 | const Tag tag; 50 | double_t utilizationThreshold; 51 | std::unique_ptr previousSamples; 52 | 53 | const std::string UTILIZATION_THRESHOLD_FILTER_ERROR = "Filter is not able" \ 54 | " to calculate total cpu usage and cut off oversubscription if needed."; 55 | 56 | const std::string UTILIZATION_THRESHOLD_FILTER_WARNING = "Filter is not" \ 57 | "able to calculate total cpu usage and will base on allocated " \ 58 | " resources to cut off oversubscription if needed."; 59 | }; 60 | 61 | } // namespace serenity 62 | } // namespace mesos 63 | 64 | #endif // SERENITY_UTILIZATION_THRESHOLD_FILTER_HPP 65 | -------------------------------------------------------------------------------- /src/filters/valve.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_VALVE_FILTER_HPP 2 | #define SERENITY_VALVE_FILTER_HPP 3 | 4 | #include 5 | 6 | #include "mesos/mesos.hpp" 7 | 8 | #include "process/future.hpp" 9 | #include "process/owned.hpp" 10 | 11 | #include "serenity/serenity.hpp" 12 | 13 | #include "stout/lambda.hpp" 14 | 15 | namespace mesos { 16 | namespace serenity { 17 | 18 | const std::string PIPELINE_ENABLE_KEY = "enabled"; 19 | const std::string VALVE_ROUTE = "/valve"; 20 | const std::string RESOURCE_ESTIMATOR_VALVE_PROCESS_BASE = 21 | "serenity_resource_estimator"; 22 | const std::string QOS_CONTROLLER_VALVE_PROCESS_BASE = 23 | "serenity_qos_controller"; 24 | 25 | 26 | static const std::string getValveProcessBaseName(const ModuleType type) { 27 | switch (type) { 28 | case RESOURCE_ESTIMATOR: 29 | return RESOURCE_ESTIMATOR_VALVE_PROCESS_BASE; 30 | case QOS_CONTROLLER: 31 | return QOS_CONTROLLER_VALVE_PROCESS_BASE; 32 | default: 33 | return "serenity_valve"; 34 | } 35 | } 36 | 37 | 38 | // Forward declaration 39 | class ValveFilterEndpointProcess; 40 | 41 | class ValveFilter : 42 | public Consumer, public Producer { 43 | public: 44 | explicit ValveFilter(bool _opened = true, 45 | const Tag& _tag = Tag(UNDEFINED, "valveFilter")); 46 | 47 | ValveFilter( 48 | Consumer* _consumer, 49 | bool _opened = true, 50 | const Tag& _tag = Tag(UNDEFINED, "valveFilter")); 51 | 52 | ~ValveFilter(); 53 | 54 | Try consume(const ResourceUsage& in); 55 | 56 | private: 57 | const Tag tag; 58 | lambda::function()> isOpened; 59 | process::Owned process; 60 | }; 61 | 62 | } // namespace serenity 63 | } // namespace mesos 64 | 65 | #endif // SERENITY_VALVE_FILTER_HPP 66 | -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/README.md: -------------------------------------------------------------------------------- 1 | ### Smoke Test Framework Guide 2 | 3 | Serenity introduces improved smoke_test_framework. 4 | It adds several features and is based on example Mesos NoExecutor framework: 5 | 6 | 1. Defining tasks scenarios using JSON files. 7 | 2. Adding URI to task. 8 | 3. Adding ability to fill all nodes resources with custom tasks. 9 | 4. Targeting task to particular host. 10 | 5. Specifying role of framework. 11 | 6. Specifying shares for each task to customize how often they should be chosen. (shares) 12 | 13 | ## Building 14 | 15 | Smoke Test Framework builds with Serenity when `-DWITH_SOURCE_MESOS=` option 16 | is specified. 17 | 18 | ## Task Specification via JSON 19 | 20 | You can run Smoke Test Framework and as the input give JSON file. 21 | Scheme: 22 | 23 | ```javascript 24 | { 25 | "tasks": [ 26 | { 27 | "command": , 28 | "uri": [Optional] { 29 | "value": ,, 30 | "executable": , 31 | "extract": , 32 | "cache": 33 | }, 34 | "taskResources": , 35 | "revocableResources": <[Optional] needed revocable resources>, 36 | "targetHostname": <[Optional] target host>, 37 | "totalTasks": <[Optional] number of tasks or if not specified - unlimited>, 38 | "shares" : <[Optional] share (priority) of the task> 39 | } 40 | ] 41 | } 42 | ``` 43 | 44 | Example (using rkt container): 45 | 46 | ```javascript 47 | { 48 | "tasks": [ 49 | { 50 | "command": "/usr/local/bin/rkt run --insecure-skip-verify --mds-register=false docker://jess/stress --exec /usr/bin/stress -- -c 1", 51 | "uri": { 52 | "value": "custom_uri", 53 | "executable": "false", 54 | "extract": "true", 55 | "cache": "false" 56 | }, 57 | "taskResources": "mem(serenity):64", 58 | "revocableResources": "cpus(serenity):1", 59 | "targetHostname": "my_super_agent@serenity.com", 60 | "totalTasks": 5, 61 | "shares" : 99 62 | } 63 | ] 64 | } 65 | ``` 66 | 67 | ## Usage 68 | 69 | You can easily run framework using CLI: 70 | 71 | Using JSON file (recommended): 72 | 73 | `./test-framework --role= --logging_level=INFO --json_path="${JSON_PATH}"` 74 | 75 | Using Args: 76 | 77 | `./test-framework --role= --logging_level=INFO --task_resources=<> 78 | --task_revocable_resources=<> --num_tasks=<> --target_hostname=<> --uri_value --command=<>` 79 | -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/run_framework.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | JSON_PATH=$1 4 | 5 | # NOTE(bplotka) valid for serenity deployment using 6 | # serenity-formulas (https://github.com/Bplotka/serenity-formula) 7 | source /opt/serenity/mesos/etc/mesos-slave 8 | export MESOS_MASTER=$MESOS_MASTER 9 | 10 | ./test-framework --logging_level=INFO --json_path="${JSON_PATH}" -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/tasks/1be_load.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "command": "/usr/local/bin/rkt run --insecure-skip-verify --mds-register=false docker://jess/stress --exec /usr/bin/stress -- -c 1", 5 | "uri": { 6 | "value": "https://raw.githubusercontent.com/mesosphere/serenity/master/README.md", 7 | "executable": false, 8 | "extract": false, 9 | "cache": false 10 | }, 11 | "taskResources": "mem(*):64", 12 | "revocableResources": "cpus(*):1", 13 | "targetHostname": "kp9.intelsdi.com", 14 | "totalTasks": 5, 15 | "shares" : 99 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/tasks/1be_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "command": "stress -c 1", 5 | "taskResources": "mem(*):64", 6 | "revocableResources": "cpus(*):1.0;", 7 | "targetHostname": "", 8 | "totalTasks": 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/tasks/1hp_and_1be_tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "command": "stress -c 1", 5 | "taskResources": "cpus(*):2;mem(*):64", 6 | "revocableResources": "", 7 | "targetHostname": "", 8 | "totalTasks": 1 9 | }, 10 | { 11 | "command": "stress -c 1", 12 | "taskResources": "mem(*):64", 13 | "revocableResources": "cpus(*):1.0", 14 | "targetHostname": "", 15 | "totalTasks": 1 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/mesos_frameworks/smoke_test/tasks/1hp_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "command": "stress -c 1", 5 | "taskResources": "cpus(*):2;mem(*):64", 6 | "revocableResources": "", 7 | "targetHostname": "", 8 | "totalTasks": 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/mesos_modules/qos_controller/serenity_controller.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_SERENITY_QOS_CONTROLLER_HPP 2 | #define SERENITY_SERENITY_QOS_CONTROLLER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mesos/slave/oversubscription.pb.h" // ONLY USEFUL AFTER RUNNING ROTOC 9 | #include "mesos/slave/qos_controller.hpp" 10 | 11 | #include "pipeline/qos_pipeline.hpp" 12 | 13 | #include "serenity/serenity.hpp" 14 | 15 | #include "stout/lambda.hpp" 16 | #include "stout/nothing.hpp" 17 | #include "stout/try.hpp" 18 | 19 | namespace mesos { 20 | namespace serenity { 21 | 22 | // Forward declaration. 23 | class SerenityControllerProcess; 24 | 25 | 26 | class SerenityController: public slave::QoSController { 27 | public: 28 | explicit SerenityController( 29 | std::shared_ptr _pipeline, 30 | double _onEmptyCorrectionInterval) 31 | : pipeline(_pipeline), 32 | onEmptyCorrectionInterval(_onEmptyCorrectionInterval) {} 33 | 34 | static Try create( 35 | std::shared_ptr _pipeline, 36 | double _onEmptyCorrectionInterval = 5) { 37 | return new SerenityController(_pipeline, _onEmptyCorrectionInterval); 38 | } 39 | 40 | virtual ~SerenityController(); 41 | 42 | virtual Try initialize( 43 | const lambda::function()>& usage); 44 | 45 | virtual process::Future> corrections(); 46 | 47 | protected: 48 | process::Owned process; 49 | std::shared_ptr pipeline; 50 | double onEmptyCorrectionInterval; 51 | }; 52 | 53 | } // namespace serenity 54 | } // namespace mesos 55 | 56 | #endif // SERENITY_SERENITY_QOS_CONTROLLER_HPP 57 | -------------------------------------------------------------------------------- /src/mesos_modules/qos_controller/serenity_controller_module.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mesos/mesos.hpp" 5 | #include "mesos/module.hpp" 6 | 7 | #include "mesos/module/qos_controller.hpp" 8 | #include "mesos/slave/qos_controller.hpp" 9 | 10 | #include "mesos_modules/qos_controller/serenity_controller.hpp" 11 | 12 | #include "pipeline/qos_pipeline.hpp" 13 | 14 | #include "serenity/config.hpp" 15 | 16 | #include "stout/try.hpp" 17 | 18 | // TODO(nnielsen): Should be explicit using-directives. 19 | using namespace mesos; // NOLINT(build/namespaces) 20 | using namespace mesos::serenity::ema; // NOLINT(build/namespaces) 21 | using namespace mesos::serenity::strategy; // NOLINT(build/namespaces) 22 | using namespace mesos::serenity::detector; // NOLINT(build/namespaces) 23 | using namespace mesos::serenity::too_low_usage; // NOLINT(build/namespaces) 24 | using namespace mesos::serenity::qos_pipeline; // NOLINT(build/namespaces) 25 | 26 | using mesos::serenity::CpuContentionStrategy; 27 | using mesos::serenity::CpuQoSPipeline; 28 | using mesos::serenity::SerenityConfig; 29 | using mesos::serenity::SerenityController; 30 | using mesos::serenity::SeniorityStrategy; 31 | using mesos::serenity::SignalBasedDetector; 32 | using mesos::serenity::TooLowUsageFilter; 33 | using mesos::serenity::QoSControllerPipeline; 34 | 35 | using mesos::slave::QoSController; 36 | 37 | 38 | // IPC QoS pipeline. 39 | static QoSController* createSerenityController( 40 | const Parameters& parameters) { 41 | LOG(INFO) << "Loading Serenity QoS Controller module"; 42 | // TODO(bplotka): Fetch configuration from parameters or conf file. 43 | // 44 | // --Hardcoded configuration for Serenity QoS Controller--- 45 | 46 | SerenityConfig conf; 47 | // AssuranceDropAnalyzer configuration: 48 | // How far we look back in samples. 49 | conf[SIGNAL_DROP_ANALYZER_NAME].set(WINDOW_SIZE, (uint64_t) 10); 50 | // Defines how much (relatively to base point) value must drop to trigger 51 | // contention. 52 | // Most signal_analyzer will use that. 53 | conf[SIGNAL_DROP_ANALYZER_NAME].set(FRACTIONAL_THRESHOLD, (double_t) 0.3); 54 | conf[SIGNAL_DROP_ANALYZER_NAME].set(SEVERITY_FRACTION, (double_t) 2.1); 55 | 56 | // How many iterations observers will wait with creating another 57 | // correction. 58 | conf[CpuContentionStrategy::NAME].set(CONTENTION_COOLDOWN, (uint64_t) 10); 59 | conf[SeniorityStrategy::NAME].set(CONTENTION_COOLDOWN, (uint64_t) 10); 60 | 61 | // UtilizationDetector configuration: 62 | // CPU utilization threshold. 63 | conf[THRESHOLD].set(THRESHOLD, (double_t) 0.72); 64 | 65 | conf[TooLowUsageFilter::NAME].set(MINIMAL_CPU_USAGE, (double_t) 0.25); 66 | 67 | conf.set(ALPHA_CPU, (double_t) 0.9); 68 | conf.set(ALPHA_IPC, (double_t) 0.9); 69 | conf.set(ENABLED_VISUALISATION, false); 70 | conf.set(VALVE_OPENED, true); 71 | 72 | // Since slave is configured for 5 second perf interval, it is useless to 73 | // check correction more often then 5 sec. 74 | double onEmptyCorrectionInterval = 2; 75 | 76 | // --End of hardcoded configuration for Serenity QoS Controller--- 77 | 78 | // Use static constructor of QoSController. 79 | Try result = 80 | SerenityController::create( 81 | std::shared_ptr( 82 | new CpuQoSPipeline(conf)), 83 | onEmptyCorrectionInterval); 84 | 85 | if (result.isError()) { 86 | return NULL; 87 | } 88 | return result.get(); 89 | } 90 | 91 | mesos::modules::Module com_mesosphere_mesos_SerenityController( 92 | MESOS_MODULE_API_VERSION, 93 | MESOS_VERSION, 94 | "Mesosphere & Intel", 95 | "support@mesosphere.com", 96 | "Serenity QoS Controller", 97 | NULL, 98 | createSerenityController); 99 | -------------------------------------------------------------------------------- /src/mesos_modules/resource_estimator/serenity_estimator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "glog/logging.h" 5 | 6 | #include "mesos_modules/resource_estimator/serenity_estimator.hpp" 7 | 8 | #include "pipeline/estimator_pipeline.hpp" 9 | 10 | #include "process/defer.hpp" 11 | #include "process/dispatch.hpp" 12 | #include "process/process.hpp" 13 | 14 | #include "stout/error.hpp" 15 | 16 | // TODO(nnielsen): Break into explicit using-declarations. 17 | using namespace process; // NOLINT(build/namespaces) 18 | 19 | namespace mesos { 20 | namespace serenity { 21 | 22 | class SerenityEstimatorProcess : 23 | public Process { 24 | public: 25 | SerenityEstimatorProcess( 26 | const lambda::function()>& _usage, 27 | std::shared_ptr _pipeline) 28 | : usage(_usage), 29 | pipeline(_pipeline) {} 30 | 31 | Future oversubscribable() { 32 | return this->usage() 33 | .then(defer(self(), &Self::_oversubscribable, lambda::_1)); 34 | } 35 | 36 | Future _oversubscribable( 37 | const Future& _resourceUsage) { 38 | Resources allocatedRevocable; 39 | foreach(auto& executor, _resourceUsage.get().executors()) { 40 | allocatedRevocable += Resources(executor.allocated()).revocable(); 41 | } 42 | 43 | Result ret = this->pipeline->run(_resourceUsage.get()); 44 | 45 | if (ret.isError()) { 46 | LOG(ERROR) << ret.error(); 47 | return Resources(); 48 | } else if (ret.isNone()) { 49 | return Resources(); 50 | } 51 | LOG(INFO) << "[SerenityEstimator] Considering allocated revocable " 52 | << "resources: " << allocatedRevocable; 53 | return (ret.get() - allocatedRevocable); 54 | } 55 | 56 | private: 57 | const lambda::function()> usage; 58 | std::shared_ptr pipeline; 59 | }; 60 | 61 | 62 | SerenityEstimator::~SerenityEstimator() { 63 | if (process.get() != NULL) { 64 | terminate(process.get()); 65 | wait(process.get()); 66 | } 67 | } 68 | 69 | 70 | Try SerenityEstimator::initialize( 71 | const lambda::function()>& usage) { 72 | if (process.get() != NULL) { 73 | return Error("Serenity estimator has already been initialized"); 74 | } 75 | 76 | process.reset(new SerenityEstimatorProcess(usage, this->pipeline)); 77 | spawn(process.get()); 78 | 79 | return Nothing(); 80 | } 81 | 82 | 83 | Future SerenityEstimator::oversubscribable() { 84 | if (process.get() == NULL) { 85 | return Failure("Serenity estimator is not initialized"); 86 | } 87 | 88 | return dispatch( 89 | process.get(), 90 | &SerenityEstimatorProcess::oversubscribable); 91 | } 92 | 93 | } // namespace serenity 94 | } // namespace mesos 95 | -------------------------------------------------------------------------------- /src/mesos_modules/resource_estimator/serenity_estimator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ESTIMATOR_SERENITY_ESTIMATOR_HPP 2 | #define ESTIMATOR_SERENITY_ESTIMATOR_HPP 3 | 4 | #include 5 | 6 | #include "mesos/slave/resource_estimator.hpp" 7 | 8 | #include "stout/lambda.hpp" 9 | #include "stout/nothing.hpp" 10 | #include "stout/try.hpp" 11 | 12 | #include "pipeline/estimator_pipeline.hpp" 13 | 14 | #include "process/future.hpp" 15 | #include "process/owned.hpp" 16 | 17 | namespace mesos { 18 | namespace serenity { 19 | 20 | // Forward declaration. 21 | class SerenityEstimatorProcess; 22 | 23 | 24 | class SerenityEstimator : public slave::ResourceEstimator { 25 | public: 26 | explicit SerenityEstimator( 27 | std::shared_ptr _pipeline) 28 | : pipeline(_pipeline) {} 29 | 30 | static Try create( 31 | std::shared_ptr _pipeline) { 32 | return new SerenityEstimator(_pipeline); 33 | } 34 | 35 | virtual ~SerenityEstimator(); 36 | 37 | virtual Try initialize( 38 | const lambda::function()>& usage); 39 | 40 | virtual process::Future oversubscribable(); 41 | 42 | protected: 43 | process::Owned process; 44 | std::shared_ptr pipeline; 45 | }; 46 | 47 | } // namespace serenity 48 | } // namespace mesos 49 | 50 | #endif // ESTIMATOR_SERENITY_ESTIMATOR_HPP 51 | -------------------------------------------------------------------------------- /src/mesos_modules/resource_estimator/serenity_estimator_module.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mesos/mesos.hpp" 5 | #include "mesos/module.hpp" 6 | 7 | #include "mesos/module/resource_estimator.hpp" 8 | #include "mesos/slave/resource_estimator.hpp" 9 | 10 | #include "mesos_modules/resource_estimator/serenity_estimator.hpp" 11 | 12 | #include "pipeline/estimator_pipeline.hpp" 13 | 14 | #include "stout/try.hpp" 15 | 16 | // TODO(nnielsen): Break up into explicit using-declarations instead. 17 | using namespace mesos; // NOLINT(build/namespaces). 18 | 19 | using mesos::serenity::CpuEstimatorPipeline; 20 | using mesos::serenity::ResourceEstimatorPipeline; 21 | using mesos::serenity::SerenityEstimator; 22 | 23 | using mesos::slave::ResourceEstimator; 24 | 25 | static ResourceEstimator* createSerenityEstimator( 26 | const Parameters& parameters) { 27 | LOG(INFO) << "Loading Serenity Estimator module"; 28 | // TODO(bplotka) Obtain the type of pipeline from parameters. 29 | 30 | Try result = SerenityEstimator::create( 31 | std::shared_ptr( 32 | new CpuEstimatorPipeline(false, true))); 33 | if (result.isError()) { 34 | return NULL; 35 | } 36 | return result.get(); 37 | } 38 | 39 | 40 | mesos::modules::Module 41 | com_mesosphere_mesos_SerenityEstimator( 42 | MESOS_MODULE_API_VERSION, 43 | MESOS_VERSION, 44 | "Mesosphere & Intel", 45 | "support@mesosphere.com", 46 | "Serenity Estimator", 47 | NULL, 48 | createSerenityEstimator); 49 | -------------------------------------------------------------------------------- /src/messages/serenity.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MESSAGES_SERENITY_HPP 2 | #define SERENITY_MESSAGES_SERENITY_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "mesos/slave/oversubscription.hpp" 8 | #include "mesos/mesos.hpp" 9 | 10 | // ONLY USEFUL AFTER RUNNING PROTOC. 11 | #include "serenity.pb.h" // NOLINT(build/include) 12 | 13 | #include "stout/option.hpp" 14 | #include "stout/none.hpp" 15 | 16 | namespace mesos { 17 | namespace serenity { 18 | 19 | #define KILL_ALL_SEVERITY 999999 20 | 21 | using Contentions = std::list; 22 | using QoSCorrections = std::list; 23 | 24 | 25 | inline Contention createContention( 26 | Option severity, 27 | const Contention_Type contentionType, 28 | Option victim = None(), 29 | Option timestamp = None()) { 30 | Contention contention; 31 | contention.set_type(contentionType); 32 | 33 | if (victim.isSome()) { 34 | contention.mutable_victim()->CopyFrom(victim.get()); 35 | } 36 | 37 | if (severity.isSome()) { 38 | contention.set_severity(severity.get()); 39 | } 40 | 41 | if (timestamp.isSome()) { 42 | contention.set_timestamp(timestamp.get()); 43 | } 44 | 45 | return contention; 46 | } 47 | 48 | 49 | inline slave::QoSCorrection_Kill createKill(const ExecutorInfo info) { 50 | slave::QoSCorrection_Kill kill; 51 | kill.mutable_framework_id()->CopyFrom(info.framework_id()); 52 | kill.mutable_executor_id()->CopyFrom(info.executor_id()); 53 | 54 | return kill; 55 | } 56 | 57 | 58 | inline WorkID createExecutorWorkID(const ExecutorInfo info) { 59 | WorkID workID; 60 | workID.mutable_framework_id()->CopyFrom(info.framework_id()); 61 | workID.mutable_executor_id()->CopyFrom(info.executor_id()); 62 | 63 | return workID; 64 | } 65 | 66 | /** 67 | * TODO(skonefal): Do we need function that accepts QoSCorrection_Kill? 68 | * Maybe we should only use ExecutorInfo parameter 69 | */ 70 | inline slave::QoSCorrection createKillQoSCorrection( 71 | slave::QoSCorrection_Kill kill_msg) { 72 | slave::QoSCorrection correction; 73 | correction.set_type(slave::QoSCorrection_Type_KILL); 74 | correction.mutable_kill()->CopyFrom(kill_msg); 75 | 76 | return correction; 77 | } 78 | 79 | 80 | static slave::QoSCorrection createKillQosCorrection( 81 | const ExecutorInfo& executorInfo) { 82 | return createKillQoSCorrection(createKill(executorInfo)); 83 | } 84 | 85 | } // namespace serenity 86 | } // namespace mesos 87 | 88 | #endif // SERENITY_MESSAGES_SERENITY_HPP 89 | -------------------------------------------------------------------------------- /src/messages/serenity.proto: -------------------------------------------------------------------------------- 1 | import "mesos/mesos.proto"; 2 | 3 | package mesos; 4 | 5 | option java_package = "org.apache.mesos"; 6 | option java_outer_classname = "Protos"; 7 | 8 | 9 | /** 10 | * Events Envelopes for EventBus. 11 | * 12 | * TODO(bplotka): Add topic for EventBus and do not specify aim of the 13 | * event by message Type, but by the topic. 14 | */ 15 | message OversubscriptionCtrlEvent { 16 | optional bool enable = 1; 17 | } 18 | 19 | message OversubscriptionCtrlEventEnvelope { 20 | optional OversubscriptionCtrlEvent message = 1; 21 | } 22 | 23 | 24 | /** 25 | * Necessary union to define job. 26 | * Currently we cannot specify individual task. 27 | * NOTE: Framework id must be set for the specified executor. 28 | */ 29 | message WorkID { 30 | optional FrameworkID framework_id = 1; 31 | optional ExecutorID executor_id = 2; 32 | } 33 | 34 | 35 | /** 36 | * Describes resource contention and noisy neighbour situation. 37 | * It describes certain type of resource starvation (cpu, cache, io) 38 | * and the victim of starvation (only a PR task is applicable). 39 | * If possible, it also points the probably aggressor executor or tasks. 40 | */ 41 | message Contention{ 42 | // TODO(nnielsen): We are mostly exercising LLC and mem bw contention at the 43 | // moment (based on different signals). Let's figure out ways to represent 44 | // that. 45 | enum Type { 46 | IPC = 1; 47 | CPU = 2; 48 | IO = 3; 49 | NETWORK = 4; 50 | } 51 | 52 | optional WorkID victim = 1; 53 | 54 | optional Type type = 2; 55 | optional double timestamp = 3; 56 | 57 | // It should reflect how many resources is needed for PR 58 | // job to not starve. 59 | // TODO(nnielsen): Which values does severity take? 60 | optional double severity = 4; 61 | } 62 | 63 | 64 | /** 65 | * Describes a collection of task performance metrics. 66 | */ 67 | message TaskPerformance { 68 | message Sample { 69 | required string name = 1; 70 | required double value = 2; 71 | optional double minimum = 3; 72 | optional double maximum = 4; 73 | } 74 | required TaskID task = 1; 75 | repeated Sample samples = 2; 76 | } 77 | -------------------------------------------------------------------------------- /src/observers/qos_correction.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bus/event_bus.hpp" 6 | 7 | #include "observers/qos_correction.hpp" 8 | 9 | #include "serenity/resource_helper.hpp" 10 | #include "serenity/utils.hpp" 11 | 12 | namespace mesos { 13 | namespace serenity { 14 | 15 | QoSCorrectionObserver::~QoSCorrectionObserver() {} 16 | 17 | void QoSCorrectionObserver::allProductsReady() { 18 | Contentions contentions = flattenListsInsideVector( 19 | Consumer::getConsumables()); 20 | Option usage = Consumer::getConsumable(); 21 | 22 | if (contentions.size() == 0 || 23 | ResourceUsageHelper::getRevocableExecutors(usage.get()).empty()) { 24 | SERENITY_LOG(INFO) << "Empty contentions received."; 25 | emptyContentionsReceived(); 26 | 27 | // Produce empty corrections and contentions 28 | produceResults(); 29 | return; 30 | } 31 | 32 | // We have contentions, but we are in "stablisation" phase. 33 | if (iterationCooldownCounter.isSome()) { 34 | SERENITY_LOG(INFO) << "QoS Correction observer is in cooldown phase"; 35 | cooldownPhase(); 36 | 37 | // Produce empty corrections and contentions 38 | produceResults(); 39 | return; 40 | } 41 | 42 | Try corrections = newContentionsReceived(); 43 | if (corrections.isError()) { 44 | SERENITY_LOG(INFO) << "corrections returned error: " << corrections.error(); 45 | // Produce empty corrections and contentions 46 | produceResults(); 47 | return; 48 | } 49 | 50 | if (corrections.get().empty()) { 51 | SERENITY_LOG(INFO) << "Strategy didn't found aggressors"; 52 | // Strategy didn't found aggressors. 53 | // Passing contentions to next QoS Controller. 54 | produceResults(QoSCorrections(), contentions); 55 | return; 56 | } 57 | 58 | // Strategy has pointed aggressors, so don't pass 59 | // current contentions to next QoS Controller. 60 | iterationCooldownCounter = this->cooldownIterations; 61 | produceResults(corrections.get(), Contentions()); 62 | } 63 | 64 | void QoSCorrectionObserver::produceResults( 65 | QoSCorrections _qosCorrections, 66 | Contentions _contentions) { 67 | produce(_qosCorrections); 68 | produce(_contentions); 69 | } 70 | 71 | void QoSCorrectionObserver::emptyContentionsReceived() { 72 | // Restart state of QoSCorrection observer 73 | if (iterationCooldownCounter.isSome()) { 74 | iterationCooldownCounter = None(); 75 | } 76 | if (estimatorPipelineDisabled) { 77 | StaticEventBus::publishOversubscriptionCtrlEvent(true); 78 | estimatorPipelineDisabled = false; 79 | } 80 | } 81 | 82 | /** 83 | * Cooldown phase - waiting for system to stabilise. 84 | */ 85 | void QoSCorrectionObserver::cooldownPhase() { 86 | if (iterationCooldownCounter.isNone()) { 87 | return; 88 | } 89 | 90 | iterationCooldownCounter.get() -= 1; 91 | if (iterationCooldownCounter.get() <= 0) { 92 | iterationCooldownCounter = None(); 93 | } 94 | return; 95 | } 96 | 97 | Try QoSCorrectionObserver::newContentionsReceived() { 98 | if (!estimatorPipelineDisabled) { 99 | // Disable Estimator pipeline. 100 | StaticEventBus::publishOversubscriptionCtrlEvent(false); 101 | estimatorPipelineDisabled = true; 102 | } 103 | 104 | Contentions contentions = flattenListsInsideVector( 105 | Consumer::getConsumables()); 106 | Option usage = Consumer::getConsumable(); 107 | return this->revocationStrategy->decide(this->executorAgeFilter, 108 | contentions, 109 | usage.get()); 110 | } 111 | 112 | } // namespace serenity 113 | } // namespace mesos 114 | -------------------------------------------------------------------------------- /src/observers/slack_resource.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "glog/logging.h" 6 | 7 | #include "mesos/mesos.hpp" 8 | 9 | #include "observers/slack_resource.hpp" 10 | 11 | #include "serenity/metrics_helper.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | Try SlackResourceObserver::consume(const ResourceUsage& usage) { 17 | std::unique_ptr newSamples(new ExecutorSet()); 18 | double_t cpuUsage = 0; 19 | double_t slackResources = 0; 20 | uint64_t oversubscrivedExecutors = 0; 21 | 22 | if (usage.total_size() == 0) { 23 | return Error(std::string(NAME) + "Cannot estimate slack resources." 24 | + "No total in ResourceUsage"); 25 | } 26 | 27 | Resources totalAgentResources(usage.total()); 28 | Option totalAgentCpus = totalAgentResources.cpus(); 29 | 30 | if (totalAgentCpus.isNone()) { 31 | return Error(std::string(NAME) + 32 | "Cannot estimate slack resources. " + 33 | "ResourceUsage does not contain " + 34 | "Agent's total CPU resource information."); 35 | } 36 | 37 | for (const auto& executor : usage.executors()) { 38 | if (executor.has_statistics() && executor.has_executor_info()) { 39 | newSamples->insert(executor); 40 | 41 | auto previousSample = this->previousSamples->find(executor); 42 | if (previousSample != this->previousSamples->end()) { 43 | Try executorCpuUsage = CountCpuUsage( 44 | *previousSample, executor); 45 | 46 | if (executorCpuUsage.isError()) { 47 | LOG(ERROR) << std::string(NAME) << ": " << executorCpuUsage.error(); 48 | continue; 49 | } 50 | cpuUsage += executorCpuUsage.get(); 51 | oversubscrivedExecutors++; 52 | 53 | if (!executor.statistics().has_cpus_limit()) { 54 | return Error(std::string(NAME) + 55 | "Cannot count slack. Lack of cpus_limit in statistcs"); 56 | } 57 | 58 | double_t executorCpuLimit = executor.statistics().cpus_limit(); 59 | double_t executorCpuSlack = executorCpuLimit - executorCpuUsage.get(); 60 | 61 | slackResources += executorCpuSlack; 62 | } 63 | } 64 | } 65 | 66 | const double_t maxSlack = 67 | (maxOversubscriptionFraction * totalAgentCpus.get()) - cpuUsage; 68 | if (maxSlack < slackResources) { 69 | slackResources = maxSlack; 70 | } else if (slackResources < SLACK_EPSILON) { 71 | slackResources = 0.0; 72 | } 73 | 74 | LOG(INFO) << std::string(NAME) << "Reporting slack value: " << slackResources 75 | << ", maxSlack: " << maxSlack << " from " << oversubscrivedExecutors 76 | << " executors."; 77 | 78 | Resource slackResult; 79 | Value_Scalar *cpuSlackScalar = new Value_Scalar(); 80 | cpuSlackScalar->set_value(slackResources); 81 | 82 | slackResult.set_name("cpus"); 83 | slackResult.set_role(this->default_role); 84 | slackResult.set_type(Value::SCALAR); 85 | slackResult.set_allocated_scalar(cpuSlackScalar); 86 | slackResult.set_allocated_revocable(new Resource_RevocableInfo()); 87 | 88 | Resources result(slackResult); 89 | 90 | produce(result); 91 | 92 | this->previousSamples->clear(); 93 | this->previousSamples = std::move(newSamples); 94 | 95 | return Nothing(); 96 | } 97 | 98 | 99 | } // namespace serenity 100 | } // namespace mesos 101 | -------------------------------------------------------------------------------- /src/observers/slack_resource.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_SLACK_RESOURCE_HPP 2 | #define SERENITY_SLACK_RESOURCE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mesos/mesos.hpp" 10 | #include "mesos/resources.hpp" 11 | 12 | #include "stout/result.hpp" 13 | 14 | #include "serenity/default_vars.hpp" 15 | #include "serenity/executor_set.hpp" 16 | #include "serenity/serenity.hpp" 17 | 18 | namespace mesos { 19 | namespace serenity { 20 | 21 | // TODO(bplotka): Make default role configurable from another source 22 | // (not only env)and as a default pass * 23 | inline std::string getDefaultRole() { 24 | if (const char* env = std::getenv("MESOS_DEFAULT_ROLE")) { 25 | return env; 26 | } 27 | return "*"; 28 | } 29 | 30 | 31 | /** 32 | * SlackResourceObserver observes incoming ResourceUsage 33 | * and produces Resource with revocable flag set (Slack Resources). 34 | * 35 | * Currently it only counts CPU slack 36 | */ 37 | class SlackResourceObserver : public Consumer, 38 | public Producer { 39 | public: 40 | explicit SlackResourceObserver( 41 | double_t _maxOversubscriptionFraction = 42 | slack_observer::DEFAULT_MAX_OVERSUBSCRIPTION_FRACTION) 43 | : previousSamples(new ExecutorSet()), 44 | maxOversubscriptionFraction(_maxOversubscriptionFraction), 45 | default_role(getDefaultRole()) {} 46 | 47 | SlackResourceObserver( 48 | Consumer* _consumer, 49 | double_t _maxOversubscriptionFraction = 50 | slack_observer::DEFAULT_MAX_OVERSUBSCRIPTION_FRACTION) : 51 | Producer(_consumer), 52 | maxOversubscriptionFraction(_maxOversubscriptionFraction), 53 | previousSamples(new ExecutorSet()), 54 | default_role(getDefaultRole()) {} 55 | 56 | ~SlackResourceObserver() {} 57 | 58 | Try consume(const ResourceUsage& usage) override; 59 | 60 | protected: 61 | std::unique_ptr previousSamples; 62 | 63 | /** 64 | * Report up to maxOversubscriptionFraction of 65 | * total Agent's CPU resources as slack resources 66 | */ 67 | double_t maxOversubscriptionFraction; 68 | 69 | /** Don't report slack when it's less than this value */ 70 | static constexpr const double_t SLACK_EPSILON = 0.001; 71 | 72 | /** Name of the class for logging purposes */ 73 | static constexpr const char* NAME = "[Serenity] SlackObserver: "; 74 | 75 | private: 76 | SlackResourceObserver(const SlackResourceObserver& other) 77 | : default_role(getDefaultRole()) {} 78 | std::string default_role; 79 | }; 80 | 81 | } // namespace serenity 82 | } // namespace mesos 83 | 84 | #endif // SERENITY_SLACK_RESOURCE_HPP 85 | -------------------------------------------------------------------------------- /src/observers/strategies/base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_STRATEGIES_DECIDER_BASE_HPP 2 | #define SERENITY_STRATEGIES_DECIDER_BASE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "filters/executor_age.hpp" 8 | 9 | #include "mesos/slave/oversubscription.hpp" 10 | 11 | #include "mesos/mesos.hpp" 12 | #include "mesos/resources.hpp" 13 | 14 | #include "messages/serenity.hpp" 15 | 16 | #include "serenity/serenity.hpp" 17 | 18 | #include "stout/try.hpp" 19 | 20 | namespace mesos { 21 | namespace serenity { 22 | 23 | /** 24 | * Base class for contention interpretations. 25 | * It converts contentions & usage to QoSCorrections. 26 | */ 27 | class RevocationStrategy { 28 | public: 29 | // TODO(skonefal): Abstract classes should not have tag. 30 | explicit RevocationStrategy(const Tag& _tag) : tag(_tag) {} 31 | 32 | virtual ~RevocationStrategy() {} 33 | 34 | // TODO(skonefal): Executor Age should be part of Resource Usage. 35 | virtual Try decide( 36 | ExecutorAgeFilter* exeutorAge, 37 | const Contentions& contentions, 38 | const ResourceUsage& usage) = 0; 39 | protected: 40 | const Tag tag; 41 | }; 42 | 43 | } // namespace serenity 44 | } // namespace mesos 45 | 46 | #endif // SERENITY_STRATEGIES_DECIDER_BASE_HPP 47 | -------------------------------------------------------------------------------- /src/observers/strategies/cache_occupancy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "bus/event_bus.hpp" 7 | 8 | #include "observers/strategies/cache_occupancy.hpp" 9 | #include "observers/strategies/seniority.hpp" 10 | 11 | #include "serenity/resource_helper.hpp" 12 | 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | using std::list; 18 | using std::pair; 19 | 20 | Try CacheOccupancyStrategy::decide( 21 | ExecutorAgeFilter* ageFilter, 22 | const Contentions& contentions, 23 | const ResourceUsage& usage) { 24 | 25 | std::vector beCmtEnabledExecutors 26 | = getCmtEnabledExecutors(ResourceUsageHelper::getRevocableExecutors(usage)); 27 | 28 | if (beCmtEnabledExecutors.empty()) { 29 | return QoSCorrections(); 30 | } 31 | 32 | double_t meanCacheOccupancy = countMeanCacheOccupancy(beCmtEnabledExecutors); 33 | std::vector aggressors = 34 | getExecutorsAboveMinimalAndMeanOccupancy(beCmtEnabledExecutors, 35 | meanCacheOccupancy); 36 | 37 | SERENITY_LOG(INFO) << "Revoking " << aggressors.size() << " executors"; 38 | QoSCorrections corrections; 39 | for (auto aggressor : aggressors) { 40 | ExecutorInfo executorInfo = aggressor.executor_info(); 41 | corrections.push_back(createKillQosCorrection(executorInfo)); 42 | 43 | std::string executorName = aggressor.executor_info().name(); 44 | SERENITY_LOG(INFO) << "Marked " << executorName << "to revoke"; 45 | } 46 | 47 | return corrections; 48 | } 49 | 50 | std::vector 51 | CacheOccupancyStrategy::getCmtEnabledExecutors( 52 | const std::list& _executors) const { 53 | std::vector executors; 54 | #ifdef CMT_ENABLED 55 | for (const ResourceUsage_Executor& executor : _executors) { 56 | if (executor.has_statistics() && 57 | executor.statistics().has_perf() && 58 | executor.statistics().perf().has_llc_occupancy()) { 59 | executors.push_back(executor); 60 | } 61 | } 62 | #endif 63 | return executors; 64 | } 65 | 66 | double_t CacheOccupancyStrategy::countMeanCacheOccupancy( 67 | const std::vector& _executors) const { 68 | double_t cacheOccupacySum = 0.0; 69 | #ifdef CMT_ENABLED 70 | for (const ResourceUsage_Executor& executor : _executors) { 71 | cacheOccupacySum += executor.statistics().perf().llc_occupancy(); 72 | } 73 | #endif 74 | return cacheOccupacySum / _executors.size(); 75 | } 76 | 77 | std::vector 78 | CacheOccupancyStrategy::getExecutorsAboveMinimalAndMeanOccupancy( 79 | const std::vector& _executors, 80 | const double_t _cacheOccupancyMean) const { 81 | std::vector product; 82 | #ifdef CMT_ENABLED 83 | for (const ResourceUsage_Executor& executor : _executors) { 84 | uint64_t cacheOccupancy = executor.statistics().perf().llc_occupancy(); 85 | if (cacheOccupancy >= _cacheOccupancyMean && 86 | cacheOccupancy > this->minimalCacheOccupancy) { 87 | product.push_back(executor); 88 | } 89 | } 90 | #endif 91 | return product; 92 | } 93 | 94 | } // namespace serenity 95 | } // namespace mesos 96 | -------------------------------------------------------------------------------- /src/observers/strategies/cache_occupancy.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_CACHE_OCCUPANCY_HPP 2 | #define SERENITY_CACHE_OCCUPANCY_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "glog/logging.h" 8 | 9 | #include "observers/strategies/base.hpp" 10 | 11 | #include "serenity/config.hpp" 12 | #include "serenity/wid.hpp" 13 | 14 | 15 | namespace mesos { 16 | namespace serenity { 17 | 18 | /** 19 | * Cache Occupancy Strategy looks at executor's LLC_OCCUPANCY and revokes 20 | * revocable jobs that are above (inclusive) mean LLC_OCCUPANCY for node. 21 | * 22 | * It returns empty QoSCorrections when there is zero BE tasks that 23 | * has llc_occupancy field in perf statistics. 24 | */ 25 | class CacheOccupancyStrategy : public RevocationStrategy { 26 | public: 27 | CacheOccupancyStrategy() : RevocationStrategy(Tag(QOS_CONTROLLER, NAME)) { 28 | init(); 29 | } 30 | 31 | explicit CacheOccupancyStrategy(const SerenityConfig& _config) 32 | : RevocationStrategy(Tag(QOS_CONTROLLER, NAME)) { 33 | init(); 34 | } 35 | 36 | Try decide(ExecutorAgeFilter* ageFilter, 37 | const Contentions& currentContentions, 38 | const ResourceUsage& currentUsage); 39 | 40 | static const constexpr char* NAME = "CacheOccupancyStrategy"; 41 | 42 | 43 | protected: 44 | void init() { 45 | minimalCacheOccupancy = DEFAULT_MINIMAL_CACHE_OCCUPANCY; 46 | } 47 | 48 | std::vector getCmtEnabledExecutors( 49 | const std::list&) const; 50 | 51 | double_t countMeanCacheOccupancy( 52 | const std::vector&) const; 53 | 54 | std::vector getExecutorsAboveMinimalAndMeanOccupancy( 55 | const std::vector& executors, 56 | const double_t meanCacheOccupancy) const; 57 | 58 | //!< Minimal cache occupancy for executor to be revoked. 59 | static constexpr uint64_t DEFAULT_MINIMAL_CACHE_OCCUPANCY = 1000000; // 1M 60 | uint64_t minimalCacheOccupancy; 61 | }; 62 | 63 | } // namespace serenity 64 | } // namespace mesos 65 | 66 | #endif // SERENITY_CACHE_OCCUPANCY_HPP 67 | -------------------------------------------------------------------------------- /src/observers/strategies/cpu_contention.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_STRATEGIES_CPU_CONTENTION_HPP 2 | #define SERENITY_STRATEGIES_CPU_CONTENTION_HPP 3 | 4 | #include "glog/logging.h" 5 | 6 | #include "observers/strategies/base.hpp" 7 | 8 | #include "serenity/config.hpp" 9 | #include "serenity/data_utils.hpp" 10 | #include "serenity/wid.hpp" 11 | 12 | namespace mesos { 13 | namespace serenity { 14 | 15 | class CpuContentionStrategyConfig : public SerenityConfig { 16 | public: 17 | CpuContentionStrategyConfig() { 18 | this->initDefaults(); 19 | } 20 | 21 | explicit CpuContentionStrategyConfig(const SerenityConfig& customCfg) { 22 | this->initDefaults(); 23 | this->applyConfig(customCfg); 24 | } 25 | 26 | void initDefaults() { 27 | // uint64_t 28 | // Specify the initial value of iterations we should wait until 29 | // we create new correction. 30 | this->fields[strategy::CONTENTION_COOLDOWN] = 31 | strategy::DEFAULT_CONTENTION_COOLDOWN; 32 | // double_t 33 | this->fields[strategy::DEFAULT_CPU_SEVERITY] = 34 | strategy::DEFAULT_DEFAULT_CPU_SEVERITY; 35 | } 36 | }; 37 | 38 | 39 | /** 40 | * Checks contentions and choose executors to kill. 41 | * It accepts only Contention_Type_CPU. 42 | * Currently, it revokes firstly executors with utilization above their limits. 43 | * Then it sorts executors by age and get max contention severity. 44 | * Each severity means how many CPUs we should 'recover' from revocation. 45 | * It introduces cooldown and also steers the valve filter using EventBus. 46 | */ 47 | class CpuContentionStrategy : public RevocationStrategy { 48 | public: 49 | explicit CpuContentionStrategy( 50 | const SerenityConfig& _config, 51 | const lambda::function& _cpuUsageGetFunction) 52 | : RevocationStrategy(Tag(QOS_CONTROLLER, "CpuContentionStrategy")), 53 | getCpuUsage(_cpuUsageGetFunction) { 54 | SerenityConfig config = CpuContentionStrategyConfig(_config); 55 | } 56 | 57 | Try decide(ExecutorAgeFilter* ageFilter, 58 | const Contentions& currentContentions, 59 | const ResourceUsage& currentUsage); 60 | 61 | static const constexpr char* NAME = "CpuContentionStrategy"; 62 | 63 | private: 64 | const lambda::function getCpuUsage; 65 | 66 | // cfg parameters. 67 | uint64_t cooldownTime; 68 | double_t defaultSeverity; 69 | }; 70 | 71 | } // namespace serenity 72 | } // namespace mesos 73 | 74 | #endif // SERENITY_STRATEGIES_CPU_CONTENTION_HPP 75 | -------------------------------------------------------------------------------- /src/observers/strategies/kill_all.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "observers/strategies/kill_all.hpp" 4 | 5 | #include "serenity/resource_helper.hpp" 6 | 7 | namespace mesos { 8 | namespace serenity { 9 | 10 | using std::list; 11 | 12 | Try KillAllStrategy::decide( 13 | ExecutorAgeFilter* ageFilter, 14 | const Contentions& currentContentions, 15 | const ResourceUsage& currentUsage) { 16 | // Product. 17 | QoSCorrections corrections; 18 | 19 | // List of BE executors. 20 | list aggressors = 21 | ResourceUsageHelper::getRevocableExecutors(currentUsage); 22 | 23 | // Create QoSCorrection from aggressors list. 24 | for (auto aggressorToKill : aggressors) { 25 | corrections.push_back(createKillQoSCorrection( 26 | createKill(aggressorToKill.executor_info()))); 27 | } 28 | 29 | return corrections; 30 | } 31 | 32 | } // namespace serenity 33 | } // namespace mesos 34 | -------------------------------------------------------------------------------- /src/observers/strategies/kill_all.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_STRATEGIES_KILL_ALL_HPP 2 | #define SERENITY_STRATEGIES_KILL_ALL_HPP 3 | 4 | #include "observers/strategies/base.hpp" 5 | 6 | namespace mesos { 7 | namespace serenity { 8 | 9 | /** 10 | * Kills all BE executors given in usage. 11 | */ 12 | class KillAllStrategy : public RevocationStrategy { 13 | public: 14 | KillAllStrategy() : 15 | RevocationStrategy(Tag(QOS_CONTROLLER, "KillAllStrategy")) {} 16 | 17 | Try decide(ExecutorAgeFilter* ageFilter, 18 | const Contentions& currentContentions, 19 | const ResourceUsage& currentUsage); 20 | }; 21 | 22 | 23 | } // namespace serenity 24 | } // namespace mesos 25 | 26 | #endif // SERENITY_STRATEGIES_KILL_ALL_HPP 27 | -------------------------------------------------------------------------------- /src/observers/strategies/seniority.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bus/event_bus.hpp" 6 | 7 | #include "observers/strategies/seniority.hpp" 8 | 9 | #include "serenity/resource_helper.hpp" 10 | 11 | namespace mesos { 12 | namespace serenity { 13 | 14 | using std::list; 15 | using std::pair; 16 | 17 | 18 | 19 | Try SeniorityStrategy::decide( 20 | ExecutorAgeFilter* ageFilter, 21 | const Contentions& currentContentions, 22 | const ResourceUsage& currentUsage) { 23 | 24 | // List of BE executors. 25 | list possibleAggressors = 26 | ResourceUsageHelper::getRevocableExecutors(currentUsage); 27 | 28 | // Aggressors to be killed. (empty for now). 29 | std::list aggressorsToKill; 30 | 31 | double_t maxSeverity = this->severity; 32 | for (const Contention contention : currentContentions) { 33 | if (contention.has_severity()) { 34 | if (contention.severity() > maxSeverity) { 35 | maxSeverity = contention.severity(); 36 | } 37 | } 38 | } 39 | 40 | // TODO(nnielsen): Made gross assumption about homogenous best-effort tasks. 41 | size_t executorsToRevokeCnt = ceil(possibleAggressors.size() * maxSeverity); 42 | if (executorsToRevokeCnt == 0) { 43 | return QoSCorrections(); 44 | } 45 | 46 | // Get ages for executors. 47 | list> executors; 48 | for (const ResourceUsage_Executor& executor : possibleAggressors) { 49 | Try age = ageFilter->age(executor.executor_info()); 50 | if (age.isError()) { 51 | LOG(WARNING) << age.error(); 52 | continue; 53 | } 54 | executors.push_back( 55 | pair(age.get(), executor)); 56 | } 57 | 58 | // TODO(nielsen): Actual time delta should be factored in i.e. not only work 59 | // as an ordering, but as a priority (taken time gaps). 60 | executors.sort([]( 61 | const pair& left, 62 | const pair& right){ 63 | return left.first < right.first; 64 | }); 65 | 66 | QoSCorrections corrections; 67 | SERENITY_LOG(INFO) << "Revoking " << executorsToRevokeCnt << " executors"; 68 | for (const auto& pair : executors) { 69 | slave::QoSCorrection correction = 70 | createKillQosCorrection(pair.second.executor_info()); 71 | corrections.push_back(correction); 72 | 73 | std::string executorName = pair.second.executor_info().name(); 74 | SERENITY_LOG(INFO) << "Marked " << executorName << "to revoke"; 75 | 76 | executorsToRevokeCnt -= 1; 77 | if (executorsToRevokeCnt == 0) { 78 | break; 79 | } 80 | } 81 | 82 | return corrections; 83 | } 84 | 85 | } // namespace serenity 86 | } // namespace mesos 87 | -------------------------------------------------------------------------------- /src/observers/strategies/seniority.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_STRATEGIES_DECIDER_SENIORITY_HPP 2 | #define SERENITY_STRATEGIES_DECIDER_SENIORITY_HPP 3 | 4 | #include "glog/logging.h" 5 | 6 | #include "observers/strategies/base.hpp" 7 | 8 | #include "serenity/config.hpp" 9 | #include "serenity/wid.hpp" 10 | 11 | namespace mesos { 12 | namespace serenity { 13 | 14 | /** 15 | * Checks contentions and choose executors to kill. 16 | * Currently it calculates mean contention and based on that estimates how 17 | * many executors we should kill. Executors are sorted by age. 18 | * 19 | * It also steers the valve filter using EventBus. 20 | */ 21 | class SeniorityStrategy : public RevocationStrategy { 22 | public: 23 | SeniorityStrategy() : RevocationStrategy(Tag(QOS_CONTROLLER, NAME)) { 24 | initialize(); 25 | } 26 | 27 | /** 28 | * TODO(skonefal): SerenityConfig should have const methods inside. 29 | * Currently, it cannot be passed as const. 30 | */ 31 | explicit SeniorityStrategy(SerenityConfig _config) 32 | : RevocationStrategy(Tag(QOS_CONTROLLER, NAME)) { 33 | initialize(); 34 | if (_config.hasKey(STARTING_SEVERITY_KEY)) { 35 | severity = _config.getD(STARTING_SEVERITY_KEY); 36 | } 37 | } 38 | 39 | Try decide(ExecutorAgeFilter*, 40 | const Contentions&, 41 | const ResourceUsage&); 42 | 43 | static const constexpr char* STARTING_SEVERITY_KEY = "STARTING_SEVERITY"; 44 | static const constexpr char* NAME = "SeniorityStrategy"; 45 | 46 | private: 47 | void initialize() { 48 | severity = DEFAULT_SEVERITY; 49 | } 50 | 51 | static const constexpr double_t DEFAULT_SEVERITY = 0.1; 52 | 53 | double_t severity; 54 | }; 55 | 56 | } // namespace serenity 57 | } // namespace mesos 58 | 59 | #endif // SERENITY_STRATEGIES_DECIDER_SENIORITY_HPP 60 | -------------------------------------------------------------------------------- /src/pipeline/estimator_pipeline.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_ESTIMATOR_PIPELINE_HPP 2 | #define SERENITY_ESTIMATOR_PIPELINE_HPP 3 | 4 | #include "filters/ignore_new_executors.hpp" 5 | #include "filters/pr_executor_pass.hpp" 6 | #include "filters/utilization_threshold.hpp" 7 | #include "filters/valve.hpp" 8 | 9 | #include "mesos/mesos.hpp" 10 | #include "mesos/resources.hpp" 11 | 12 | #include "observers/slack_resource.hpp" 13 | 14 | #include "pipeline/pipeline.hpp" 15 | 16 | #include "serenity/default_vars.hpp" 17 | #include "serenity/serenity.hpp" 18 | 19 | #include "time_series_export/slack_ts_export.hpp" 20 | 21 | namespace mesos { 22 | namespace serenity { 23 | 24 | using ResourceEstimatorPipeline = Pipeline; 25 | 26 | /** 27 | * Pipeline which includes necessary filters for cpu estimation. 28 | * {{ PIPELINE SOURCE }} 29 | * | 30 | * |ResourceUsage| 31 | * | 32 | * {{ Valve }} (+http endpoint) // First item. 33 | * | 34 | * |ResourceUsage| 35 | * | 36 | * {{ Utilization Filter }} // 2nd item. 37 | * | 38 | * |ResourceUsage| 39 | * | 40 | * {{ PR Executors Pass }} //3rd item. 41 | * | 42 | * |ResourceUsage| 43 | * | 44 | * {{ Ignore New Executors }} //4th item. 45 | * | 46 | * |ResourceUsage| 47 | * | 48 | * {{ Slack Observer }} // Last item. 49 | * | \ 50 | * |Resources| [Resources] 51 | * | | 52 | * {{ PIPELINE SINK }} {{ Slack Time Series Export }} 53 | * 54 | * For detailed schema please see: docs/pipeline.md 55 | */ 56 | class CpuEstimatorPipeline : public ResourceEstimatorPipeline { 57 | public: 58 | explicit CpuEstimatorPipeline( 59 | double_t _newExecutorsThreshold = new_executor::DEFAULT_THRESHOLD_SEC, 60 | double_t _utilizationThreshold = utilization::DEFAULT_THRESHOLD, 61 | bool _visualisation = false, 62 | bool _valveOpened = true) : 63 | // Time series exporters. 64 | slackTimeSeriesExporter(), 65 | // Last item in pipeline. 66 | slackObserver(this, 0.7), 67 | // 4th item in pipeline. 68 | ignoreNewExecutorsFilter(&slackObserver), 69 | // 3rd item in pipeline. 70 | prExecutorPassFilter(&ignoreNewExecutorsFilter), 71 | // 2nd item in pipeline. 72 | utilizationFilter( 73 | &prExecutorPassFilter, 74 | _utilizationThreshold, 75 | Tag(RESOURCE_ESTIMATOR, "utilizationFilter")), 76 | // First item in pipeline. 77 | valveFilter( 78 | &utilizationFilter, 79 | _valveOpened, 80 | Tag(RESOURCE_ESTIMATOR, "valveFilter")) { 81 | // NOTE(bplotka): Currently we wait one minute for testing purposes. 82 | // However in production env 5 minutes is a better value. 83 | this->ignoreNewExecutorsFilter.setThreshold(_newExecutorsThreshold); 84 | // Setup beginning producer. 85 | this->addConsumer(&valveFilter); 86 | // Setup Time Series Exports 87 | if (_visualisation) { 88 | slackObserver.addConsumer(&slackTimeSeriesExporter); 89 | } 90 | } 91 | 92 | private: 93 | // --- Time Series Exporters --- 94 | SlackTimeSeriesExporter slackTimeSeriesExporter; 95 | 96 | // --- Observers --- 97 | SlackResourceObserver slackObserver; 98 | 99 | // --- Filters --- 100 | IgnoreNewExecutorsFilter ignoreNewExecutorsFilter; 101 | PrExecutorPassFilter prExecutorPassFilter; 102 | UtilizationThresholdFilter utilizationFilter; 103 | ValveFilter valveFilter; 104 | }; 105 | 106 | } // namespace serenity 107 | } // namespace mesos 108 | 109 | #endif // SERENITY_ESTIMATOR_PIPELINE_HPP 110 | -------------------------------------------------------------------------------- /src/pipeline/pipeline.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_PIPELINE_HPP 2 | #define SERENITY_PIPELINE_HPP 3 | 4 | #include "bus/event_bus.hpp" 5 | 6 | #include "serenity/serenity.hpp" 7 | 8 | #include "stout/error.hpp" 9 | #include "stout/nothing.hpp" 10 | #include "stout/result.hpp" 11 | #include "stout/try.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | /** 17 | * Base class for pipeline. It becomes source and sink in the 18 | * same time to integrate with filters within the module. 19 | * In order to introduce a new pipeline using this base class, filters need 20 | * to be connected to this instance at the beginning and the end of pipeline. 21 | * 22 | * Product is a type of object for pipeline feed. 23 | * Consumable is a type of object which is consumed at the end of pipeline. 24 | */ 25 | template 26 | class Pipeline : public Producer, public Consumer { 27 | public: 28 | virtual ~Pipeline() {} 29 | 30 | virtual Result run(const Product& _product) { 31 | // Reset result. 32 | this->result = None(); 33 | 34 | // Start pipeline. 35 | // TODO(skonefal): Pipeline should not "return" with error. 36 | Try ret = this->produce(_product); 37 | 38 | if (ret.isError()) { 39 | return Error(ret.error()); 40 | } 41 | 42 | return this->result; 43 | } 44 | 45 | virtual Try consume(const Consumable& in) { 46 | // Save consumed product at the end of pipeline as a result. 47 | this->result = in; 48 | 49 | return Nothing(); 50 | } 51 | 52 | protected: 53 | Option result; 54 | }; 55 | 56 | } // namespace serenity 57 | } // namespace mesos 58 | 59 | #endif // SERENITY_PIPELINE_HPP 60 | -------------------------------------------------------------------------------- /src/serenity/agent_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "agent_utils.hpp" 4 | 5 | namespace mesos { 6 | namespace serenity { 7 | 8 | std::mutex AgentInfo::connectionMutex; 9 | Option AgentInfo::hostname = None(); 10 | Option AgentInfo::agentId = None(); 11 | 12 | } // namespace serenity 13 | } // namespace mesos 14 | -------------------------------------------------------------------------------- /src/serenity/agent_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_AGENT_UTILS_HPP 2 | #define SERENITY_AGENT_UTILS_HPP 3 | 4 | #include 5 | #include // NOLINT [build/c++11] 6 | #include 7 | 8 | #include "curl_easy.h" // NOLINT [build/include] 9 | 10 | #include "glog/logging.h" 11 | 12 | #include "rapidjson/document.h" 13 | 14 | #include "serenity/os_utils.hpp" 15 | 16 | #include "stout/error.hpp" 17 | #include "stout/nothing.hpp" 18 | #include "stout/option.hpp" 19 | #include "stout/result.hpp" 20 | #include "stout/try.hpp" 21 | 22 | namespace mesos { 23 | namespace serenity { 24 | 25 | class AgentInfo { 26 | public: 27 | static Try GetHostName() { 28 | std::lock_guard lock(connectionMutex); 29 | if (hostname.isNone()) { 30 | Try result = FillAgentInfo(); 31 | if (result.isError()) { 32 | return result.error(); 33 | } 34 | } 35 | return hostname.get(); 36 | } 37 | 38 | 39 | static Try GetAgentId() { 40 | std::lock_guard lock(connectionMutex); 41 | if (agentId.isNone()) { 42 | Try result = FillAgentInfo(); 43 | if (result.isError()) { 44 | return result.error(); 45 | } 46 | } 47 | return agentId.get(); 48 | } 49 | 50 | protected: 51 | static Try FillAgentInfo() { 52 | Try result = GetStateFromAgent(); 53 | if (result.isError()) { 54 | return Error(result.error()); 55 | } else { 56 | rapidjson::Document doc; 57 | doc.Parse(result.get().c_str()); 58 | if (!doc.IsObject() 59 | || !doc["hostname"].IsString() 60 | || !doc["id"].IsString()) { 61 | return Error("Could not parse /state.json endpoint"); 62 | } 63 | 64 | hostname = (doc["hostname"]).GetString(); 65 | agentId = (doc["id"]).GetString(); 66 | 67 | return Nothing(); 68 | } 69 | } 70 | 71 | 72 | static Try GetStateFromAgent() { 73 | // TODO(skonefal): Add auto discovery of local mesos agent IP and port 74 | Try hostname = GetHostname(); 75 | if (hostname.isError()) { 76 | LOG(ERROR) << "Could not get hostname"; 77 | return Error("Could not get hostname"); 78 | } 79 | std::string agentUrl = hostname.get() + ":5051/state.json"; 80 | 81 | std::ostringstream responseStream; 82 | curl::curl_writer writer(responseStream); 83 | curl::curl_easy easy(writer); 84 | 85 | easy.add(curl_pair(CURLOPT_URL, agentUrl)); 86 | easy.add(curl_pair(CURLOPT_FOLLOWLOCATION, 1L)); 87 | easy.add(curl_pair(CURLOPT_HTTPGET, 1L)); 88 | try { 89 | easy.perform(); 90 | } 91 | catch (curl_easy_exception error) { 92 | LOG(ERROR) << "Error while executing GET on " << agentUrl << "\n" 93 | << error.what(); 94 | return Error("Error while executing GET on " + agentUrl + "\n" 95 | + error.what()); 96 | } 97 | 98 | return responseStream.str(); 99 | } 100 | 101 | 102 | static std::mutex connectionMutex; 103 | 104 | static Option hostname; 105 | static Option agentId; 106 | }; 107 | 108 | 109 | } // namespace serenity 110 | } // namespace mesos 111 | 112 | 113 | #endif // SERENITY_AGENT_UTILS_HPP 114 | -------------------------------------------------------------------------------- /src/serenity/default_vars.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_DEFAULT_VARS_HPP 2 | #define SERENITY_DEFAULT_VARS_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace mesos { 8 | namespace serenity { 9 | 10 | namespace qos_pipeline { 11 | const constexpr char* VALVE_OPENED = "VALVE_OPENED"; 12 | constexpr bool DEFAULT_VALVE_OPENED = true; 13 | const constexpr char* ENABLED_VISUALISATION = "ENABLED_VISUALISATION"; 14 | constexpr bool DEFAULT_ENABLED_VISUALISATION = true; 15 | } // namespace qos_pipeline 16 | 17 | 18 | namespace ema { 19 | /** 20 | * Alpha controls how long is the moving average period. 21 | * The smaller alpha becomes, the longer your moving average is. 22 | * It becomes smoother, but less reactive to new samples. 23 | */ 24 | const constexpr char* ALPHA = "ALPHA"; 25 | constexpr double_t DEFAULT_ALPHA = 0.2; 26 | 27 | const constexpr char* ALPHA_CPU = "ALPHA_CPU"; 28 | const constexpr char* ALPHA_IPC = "ALPHA_IPC"; 29 | 30 | } // namespace ema 31 | 32 | namespace detector { 33 | const constexpr char* ANALYZER_TYPE = "ANALYZER_TYPE"; 34 | const constexpr char* WINDOW_SIZE = "WINDOW_SIZE"; 35 | constexpr uint64_t DEFAULT_WINDOW_SIZE = 10; 36 | const constexpr char* FRACTIONAL_THRESHOLD = "FRACTIONAL_THRESHOLD"; 37 | constexpr double_t DEFAULT_FRACTIONAL_THRESHOLD = 0.5; 38 | const constexpr char* SEVERITY_FRACTION = "SEVERITY_FRACTION"; 39 | constexpr double_t DEFAULT_SEVERITY_FRACTION = -1; 40 | const constexpr char* NEAR_FRACTION = "NEAR_FRACTION"; 41 | constexpr double_t DEFAULT_NEAR_FRACTION = 0.1; 42 | const constexpr char* MAX_CHECKPOINTS = "MAX_CHECKPOINTS"; 43 | constexpr uint64_t DEFAULT_MAX_CHECKPOINTS = 3; 44 | const constexpr char* QUORUM = "QUORUM"; 45 | constexpr double_t DEFAULT_QUORUM = 0.70; 46 | 47 | constexpr double_t DEFAULT_START_VALUE = 0.00001; 48 | 49 | const constexpr char* THRESHOLD = "THRESHOLD"; 50 | constexpr double_t DEFAULT_UTILIZATION_THRESHOLD = 0.85; 51 | } // namespace detector 52 | 53 | namespace slack_observer { 54 | constexpr double_t DEFAULT_MAX_OVERSUBSCRIPTION_FRACTION = 0.8; 55 | } // namespace slack_observer 56 | 57 | namespace utilization { 58 | constexpr double_t DEFAULT_THRESHOLD = 0.95; 59 | } // namespace utilization 60 | 61 | 62 | namespace new_executor { 63 | constexpr uint32_t DEFAULT_THRESHOLD_SEC = 5 * 60; // !< Five minutes. 64 | } // namespace new_executor 65 | 66 | namespace too_low_usage { 67 | const constexpr char* MINIMAL_CPU_USAGE = "MINIMAL_CPU_USAGE"; 68 | constexpr double_t DEFAULT_MINIMAL_CPU_USAGE = 0.25; // !< per sec. 69 | } // namespace too_low_usage 70 | 71 | namespace strategy { 72 | const constexpr char* CONTENTION_COOLDOWN = "CONTENTION_COOLDOWN"; 73 | constexpr uint64_t DEFAULT_CONTENTION_COOLDOWN = 10; 74 | const constexpr char* DEFAULT_CPU_SEVERITY = "DEFAULT_CPU_SEVERITY"; 75 | constexpr double_t DEFAULT_DEFAULT_CPU_SEVERITY = 1.0; 76 | static const constexpr char* STARTING_SEVERITY = "STARTING_SEVERITY"; 77 | constexpr double_t DEFAULT_STARTING_SEVERITY = 0.1; 78 | } // namespace strategy 79 | 80 | } // namespace serenity 81 | } // namespace mesos 82 | 83 | #endif // SERENITY_DEFAULT_VARS_HPP 84 | -------------------------------------------------------------------------------- /src/serenity/executor_map.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_EXECUTOR_MAP_HPP 2 | #define SERENITY_EXECUTOR_MAP_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace mesos { 9 | namespace serenity { 10 | 11 | /** 12 | * Hasher functor for Executor Info 13 | */ 14 | struct ExecutorInfoHasher{ 15 | size_t operator()(const ExecutorInfo& that) const { 16 | std::string hashKey = that.executor_id().value() + 17 | that.framework_id().value(); 18 | std::hash hashFunc; 19 | return hashFunc(hashKey); 20 | } 21 | }; 22 | 23 | 24 | /** 25 | * Equals functor for Executor Info 26 | */ 27 | struct ExecutorInfoEquals { 28 | bool operator()(const ExecutorInfo& lhs, 29 | const ExecutorInfo& rhs) const { 30 | return (lhs.executor_id().value() == 31 | rhs.executor_id().value()) && 32 | (lhs.framework_id().value() == 33 | rhs.framework_id().value()); 34 | } 35 | }; 36 | 37 | 38 | /** 39 | * Unordered map for storing objects where ExecutorInfo is the key. 40 | */ 41 | template 42 | using ExecutorMap = std::unordered_map; 46 | 47 | } // namespace serenity 48 | } // namespace mesos 49 | 50 | #endif // SERENITY_EXECUTOR_MAP_HPP 51 | -------------------------------------------------------------------------------- /src/serenity/executor_set.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_EXECUTOR_SET_HPP 2 | #define SERENITY_EXECUTOR_SET_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "mesos/mesos.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | 12 | /** 13 | * Hasher functor for ExecutorSet 14 | */ 15 | struct ExecutorSetHasher{ 16 | size_t operator()(const ResourceUsage_Executor& that) const { 17 | if (!that.has_executor_info()) { 18 | return 0; 19 | } else { 20 | std::string hashKey = that.executor_info().executor_id().value() + 21 | that.executor_info().framework_id().value(); 22 | std::hash hashFunc; 23 | return hashFunc(hashKey); 24 | } 25 | } 26 | }; 27 | 28 | /** 29 | * Equals functor for ExecutorSet 30 | */ 31 | struct ExecutorSetEquals { 32 | bool operator()(const ResourceUsage_Executor& lhs, 33 | const ResourceUsage_Executor& rhs) const { 34 | if (!lhs.has_executor_info() && !rhs.has_executor_info()) { 35 | return true; 36 | } else { 37 | return (lhs.has_executor_info() == rhs.has_executor_info()) && 38 | (lhs.executor_info().executor_id().value() == 39 | rhs.executor_info().executor_id().value()) && 40 | (lhs.executor_info().framework_id().value() == 41 | rhs.executor_info().framework_id().value()); 42 | } 43 | } 44 | }; 45 | 46 | /** 47 | * Unordered set for storing ResourceUsage_Executor objects. 48 | * It has Hash and Equals function based on frameworkId and executorId. 49 | * 50 | * Don't put here objects without executor_info() or the equals and hashcode 51 | * function won't have any sense. 52 | */ 53 | typedef std::unordered_set ExecutorSet; 56 | 57 | } // namespace serenity 58 | } // namespace mesos 59 | 60 | #endif // SERENITY_EXECUTOR_SET_HPP 61 | -------------------------------------------------------------------------------- /src/serenity/math_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MATH_UTILS_HPP 2 | #define SERENITY_MATH_UTILS_HPP 3 | 4 | #include 5 | 6 | namespace mesos { 7 | namespace serenity { 8 | namespace utils { 9 | 10 | /** 11 | * Default epsilon for near equality for two double_t values in 12 | * AlmostEq and AlmostZero functions. 13 | */ 14 | constexpr double_t DEFAULT_EPSILON = std::numeric_limits::epsilon(); 15 | 16 | /** 17 | * Returns near equality between two double_t values. 18 | * Default epsilon is std::numeric_limits::epsilon() 19 | */ 20 | inline bool AlmostEq( 21 | double_t lhs, 22 | double_t rhs, 23 | double_t epsilon = DEFAULT_EPSILON) { 24 | return std::abs(lhs - rhs) < epsilon; 25 | } 26 | 27 | /** 28 | * Returns near equality between double_t and 0.0 value. 29 | * Default epsilon is std::numeric_limits::epsilon() 30 | */ 31 | inline bool AlmostZero( 32 | double_t lhs, 33 | double_t epsilon = DEFAULT_EPSILON) { 34 | return AlmostEq(lhs, 0.0, epsilon); 35 | } 36 | 37 | } // namespace utils 38 | } // namespace serenity 39 | } // namespace mesos 40 | 41 | #endif // SERENITY_MATH_UTILS_HPP 42 | -------------------------------------------------------------------------------- /src/serenity/os_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_OS_UTILS_HPP 2 | #define SERENITY_OS_UTILS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "stout/none.hpp" 8 | #include "stout/option.hpp" 9 | #include "stout/try.hpp" 10 | 11 | 12 | namespace mesos { 13 | namespace serenity { 14 | 15 | inline static Option GetEnviromentVariable( 16 | const std::string& _enviromentVariable) { 17 | if (const char* env = std::getenv(_enviromentVariable.c_str())) { 18 | return env; 19 | } 20 | return None(); 21 | } 22 | 23 | inline static Try GetHostname() { 24 | constexpr size_t BUF_SIZE = 32; 25 | char buf[BUF_SIZE]; 26 | 27 | int result = gethostname(buf, BUF_SIZE); 28 | if (result != 0) { 29 | return Error("Could not get local hostname"); 30 | } 31 | return std::string(buf); 32 | } 33 | 34 | } // namespace serenity 35 | } // namespace mesos 36 | 37 | #endif // SERENITY_OS_UTILS_HPP 38 | -------------------------------------------------------------------------------- /src/serenity/resource_helper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mesos/mesos.hpp" 5 | #include "mesos/resources.hpp" 6 | 7 | #include "serenity/resource_helper.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | 12 | std::list 13 | ResourceUsageHelper::getRevocableExecutors( 14 | const ResourceUsage& usage) { 15 | auto executorListsTuple = getProductionAndRevocableExecutors(usage); 16 | return std::get(executorListsTuple); 17 | } 18 | 19 | std::list 20 | ResourceUsageHelper::getProductionExecutors( 21 | const ResourceUsage& usage) { 22 | auto executorListsTuple = getProductionAndRevocableExecutors(usage); 23 | return std::get(executorListsTuple); 24 | } 25 | 26 | std::tuple< 27 | std::list, 28 | std::list> 29 | ResourceUsageHelper::getProductionAndRevocableExecutors( 30 | const ResourceUsage& usage) { 31 | std::list productionExecutors; 32 | std::list revocableExecutors; 33 | for (ResourceUsage_Executor executor : usage.executors()) { 34 | Resources allocated(executor.allocated()); 35 | 36 | if (allocated.revocable().empty()) { 37 | productionExecutors.push_back(executor); 38 | } else { 39 | revocableExecutors.push_back(executor); 40 | } 41 | } 42 | return std::make_tuple(productionExecutors, 43 | revocableExecutors); 44 | } 45 | 46 | Try ResourceUsageHelper::isProductionExecutor( 47 | const ResourceUsage_Executor& executor) { 48 | if (executor.allocated().size() == 0) { 49 | return Error("Executor has no allocated resources."); 50 | } 51 | 52 | if (Resources(executor.allocated()).revocable().empty()) { 53 | return true; 54 | } else { 55 | return false; 56 | } 57 | } 58 | 59 | bool ResourceUsageHelper::isExecutorHasStatistics( 60 | const ResourceUsage_Executor& executor) { 61 | if (executor.has_executor_info() && executor.has_statistics()) { 62 | return true; 63 | } else { 64 | return false; 65 | } 66 | } 67 | 68 | Try ResourceUsageHelper::isRevocableExecutor( 69 | const ResourceUsage_Executor &executor) { 70 | Try result = isProductionExecutor(executor); 71 | if (result.isError()) { 72 | return result; 73 | } 74 | 75 | return !result.get(); 76 | } 77 | 78 | } // namespace serenity 79 | } // namespace mesos 80 | -------------------------------------------------------------------------------- /src/serenity/resource_helper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_RESOURCE_HELPER_HPP 2 | #define SERENITY_RESOURCE_HELPER_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "mesos/mesos.hpp" 8 | #include "mesos/resources.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | 13 | class ResourceUsageHelper { 14 | public: 15 | /** 16 | * Note: Drops executors that does not have allocated resources. 17 | */ 18 | static std::list getRevocableExecutors( 19 | const ResourceUsage&); 20 | 21 | /** 22 | * Note: Drops executors that does not have allocated resources. 23 | */ 24 | static std::list getProductionExecutors( 25 | const ResourceUsage&); 26 | 27 | /** 28 | * Returns tuple of , list> executors. 29 | * Note, that it drops executors that does not have allocated 30 | * resources. 31 | */ 32 | static std::tuple, 33 | std::list> 34 | getProductionAndRevocableExecutors(const ResourceUsage&); 35 | 36 | /** 37 | * Checks if executor has empty revocable resources. 38 | * 39 | * Returns error when executor has no allocated resources. 40 | */ 41 | static Try isProductionExecutor(const ResourceUsage_Executor&); 42 | 43 | /** 44 | * Checks if executor has revocable resources. 45 | * 46 | * Returns error when executor has no allocated resources. 47 | */ 48 | static Try isRevocableExecutor(const ResourceUsage_Executor&); 49 | 50 | static bool isExecutorHasStatistics(const ResourceUsage_Executor&); 51 | 52 | enum ExecutorType : int { 53 | PRODUCTION = 0, 54 | REVOCABLE = 1 55 | }; 56 | }; 57 | 58 | } // namespace serenity 59 | } // namespace mesos 60 | 61 | #endif // SERENITY_RESOURCE_HELPER_HPP 62 | -------------------------------------------------------------------------------- /src/serenity/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_UTILS_HPP 2 | #define SERENITY_UTILS_HPP 3 | 4 | #include 5 | 6 | template 7 | T flattenListsInsideVector(std::vector _vectorOfContainers) { 8 | T products; 9 | for (T items : _vectorOfContainers) { 10 | for (auto item : items) { 11 | products.push_back(item); 12 | } 13 | } 14 | return products; 15 | } 16 | 17 | #endif // SERENITY_UTILS_HPP 18 | -------------------------------------------------------------------------------- /src/serenity/wid.cpp: -------------------------------------------------------------------------------- 1 | #include "serenity/wid.hpp" 2 | 3 | namespace mesos { 4 | namespace serenity { 5 | 6 | bool operator == (const WID& lhs, const WID& rhs) { 7 | if (lhs.framework_id.value() != rhs.framework_id.value()) 8 | return false; 9 | if (lhs.executor_id.value() != rhs.executor_id.value()) 10 | return false; 11 | 12 | return true; 13 | } 14 | 15 | bool operator != (const WID& lhs, const WID& rhs) { 16 | return !(lhs == rhs); 17 | } 18 | 19 | } // namespace serenity 20 | } // namespace mesos 21 | -------------------------------------------------------------------------------- /src/serenity/wid.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_WID_HPP 2 | #define SERENITY_WID_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mesos/slave/oversubscription.hpp" 9 | #include "mesos/mesos.hpp" 10 | 11 | #include "messages/serenity.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | class WID; 17 | 18 | bool operator == (const WID& lhs, const WID& rhs); 19 | 20 | 21 | bool operator != (const WID& lhs, const WID& rhs); 22 | 23 | /** 24 | * Serenity Work ID wrapper. 25 | * It defined universal Executor Id. 26 | */ 27 | class WID { 28 | public: 29 | //! ExecutorInfo. 30 | explicit WID(const ExecutorInfo& that) { 31 | this->executor_id.CopyFrom(that.executor_id()); 32 | this->framework_id.CopyFrom(that.framework_id()); 33 | } 34 | 35 | //! WorkID. 36 | explicit WID(const WorkID& that) { 37 | this->executor_id.CopyFrom(that.executor_id()); 38 | this->framework_id.CopyFrom(that.framework_id()); 39 | } 40 | 41 | //! slave::QoSCorrection_Kill. 42 | explicit WID(const slave::QoSCorrection_Kill& that) { 43 | this->executor_id.CopyFrom(that.executor_id()); 44 | this->framework_id.CopyFrom(that.framework_id()); 45 | } 46 | 47 | //! WorkID getter. 48 | inline mesos::WorkID getWorkID() const { 49 | WorkID exported; 50 | exported.mutable_executor_id()->CopyFrom(this->executor_id); 51 | exported.mutable_framework_id()->CopyFrom(this->framework_id); 52 | return exported; 53 | } 54 | 55 | //! slave::QoSCorrection_Kill getter. 56 | inline slave::QoSCorrection_Kill getKill() { 57 | slave::QoSCorrection_Kill exported; 58 | exported.mutable_executor_id()->CopyFrom(this->executor_id); 59 | exported.mutable_framework_id()->CopyFrom(this->framework_id); 60 | return exported; 61 | } 62 | 63 | //! ExecutorInfo getter. 64 | inline ExecutorInfo getExecutorInfo() { 65 | ExecutorInfo exported; 66 | exported.mutable_executor_id()->CopyFrom(this->executor_id); 67 | exported.mutable_framework_id()->CopyFrom(this->framework_id); 68 | return exported; 69 | } 70 | 71 | inline std::string toString() { 72 | return executor_id.value() + " FrameworkID(" + framework_id.value() + ")"; 73 | } 74 | 75 | ExecutorID executor_id; 76 | FrameworkID framework_id; 77 | }; 78 | 79 | 80 | struct WIDHasher { 81 | //! Hasher for WorkID. 82 | size_t operator()(const WorkID& that) const { 83 | std::string hashKey = that.executor_id().value() + 84 | that.framework_id().value(); 85 | std::hash hashFunc; 86 | return hashFunc(hashKey); 87 | } 88 | }; 89 | 90 | } // namespace serenity 91 | } // namespace mesos 92 | 93 | #endif // SERENITY_WID_HPP 94 | -------------------------------------------------------------------------------- /src/tests/bus/event_bus_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "bus/event_bus.hpp" 2 | 3 | #include "filters/valve.hpp" 4 | 5 | #include "gtest/gtest.h" 6 | #include "gmock/gmock.h" 7 | 8 | #include "glog/logging.h" 9 | 10 | #include "messages/serenity.hpp" 11 | 12 | #include "mesos/mesos.hpp" 13 | 14 | #include "process/clock.hpp" 15 | #include "process/gtest.hpp" 16 | #include "process/process.hpp" 17 | 18 | namespace mesos { 19 | namespace serenity { 20 | namespace tests { 21 | 22 | using ::testing::_; 23 | using ::testing::Return; 24 | 25 | class TestEventConsumer : 26 | public ProtobufProcess { 27 | public: 28 | explicit TestEventConsumer(bool enabled) 29 | : ProtobufProcess(), 30 | oversubscription_enabled(enabled) { 31 | install( 32 | &TestEventConsumer::event, 33 | &OversubscriptionCtrlEventEnvelope::message); 34 | } 35 | 36 | bool oversubscription_enabled; 37 | 38 | void event(const OversubscriptionCtrlEvent& msg) { 39 | LOG(INFO) << "Got Message!"; 40 | this->oversubscription_enabled = msg.enable(); 41 | } 42 | }; 43 | 44 | /** 45 | * Subscribe for an OversubscriptionCtrlEventEnvelope and check if published event 46 | * of that type will be receive by subscriber. 47 | */ 48 | TEST(EventBus, SubscribeAndPublish) { 49 | // Create consumer with endpoint installed. 50 | TestEventConsumer consumer(false); 51 | process::spawn(consumer); 52 | 53 | // Subscribe for OversubscriptionCtrlEventEnvelope messages. 54 | StaticEventBus::subscribe(consumer.self()); 55 | 56 | // Prepare message to enable oversubscription. 57 | OversubscriptionCtrlEventEnvelope envelope; 58 | envelope.mutable_message()->set_enable(true); 59 | StaticEventBus::publish(envelope); 60 | 61 | // Wait for libprocess queue to be processed. 62 | process::Clock::pause(); 63 | process::Clock::settle(); 64 | 65 | EXPECT_TRUE(consumer.oversubscription_enabled); 66 | 67 | // Disable oversubscription. 68 | envelope.mutable_message()->set_enable(false); 69 | StaticEventBus::publish(envelope); 70 | 71 | // Wait for libprocess queue to be processed. 72 | process::Clock::pause(); 73 | process::Clock::settle(); 74 | 75 | EXPECT_FALSE(consumer.oversubscription_enabled); 76 | 77 | // Clear Clock. 78 | process::Clock::resume(); 79 | 80 | // Release libprocess threads. 81 | process::terminate(consumer); 82 | process::wait(consumer); 83 | } 84 | 85 | } // namespace tests 86 | } // namespace serenity 87 | } // namespace mesos 88 | 89 | -------------------------------------------------------------------------------- /src/tests/common/config_helper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_TESTS_CONFIG_HELPER_HPP 2 | #define SERENITY_TESTS_CONFIG_HELPER_HPP 3 | 4 | #include 5 | 6 | #include "serenity/config.hpp" 7 | 8 | namespace mesos { 9 | namespace serenity { 10 | namespace tests { 11 | 12 | inline SerenityConfig createAssuranceAnalyzerCfg( 13 | const uint64_t windowSize, 14 | const uint64_t maxCheckpoints, 15 | const double_t fractionalThreshold, 16 | const double_t severityLvl = detector::DEFAULT_SEVERITY_FRACTION, 17 | const double_t nearLvl = detector::DEFAULT_NEAR_FRACTION, 18 | const double_t quorum = detector::DEFAULT_QUORUM) { 19 | SerenityConfig cfg; 20 | cfg.set(detector::WINDOW_SIZE, windowSize); 21 | cfg.set(detector::MAX_CHECKPOINTS, maxCheckpoints); 22 | cfg.set(detector::FRACTIONAL_THRESHOLD, fractionalThreshold); 23 | cfg.set(detector::SEVERITY_FRACTION, severityLvl); 24 | cfg.set(detector::NEAR_FRACTION, nearLvl); 25 | cfg.set(detector::QUORUM, quorum); 26 | 27 | return cfg; 28 | } 29 | 30 | inline SerenityConfig createThresholdDetectorCfg( 31 | const double_t utilization = detector::DEFAULT_UTILIZATION_THRESHOLD) { 32 | SerenityConfig cfg; 33 | cfg.set(detector::THRESHOLD, utilization); 34 | 35 | return cfg; 36 | } 37 | 38 | } // namespace tests 39 | } // namespace serenity 40 | } // namespace mesos 41 | 42 | #endif // SERENITY_TESTS_CONFIG_HELPER_HPP 43 | -------------------------------------------------------------------------------- /src/tests/common/mocks/mock_consumer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MOCK_CONSUMER_HPP 2 | #define SERENITY_MOCK_CONSUMER_HPP 3 | 4 | #include 5 | 6 | #include "gmock/gmock.h" 7 | 8 | #include "serenity/serenity.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | template 15 | class MockConsumer : public Consumer { 16 | public: 17 | MOCK_METHOD0(allProductsReady, void()); 18 | MOCK_METHOD1_T(consume, Try(const C1&)); 19 | 20 | const std::vector& getConsumables() const { 21 | return Consumer::getConsumables(); 22 | } 23 | 24 | Option getConsumable() const { 25 | return Consumer::getConsumable(); 26 | } 27 | }; 28 | 29 | } // namespace tests 30 | } // namespace serenity 31 | } // namespace mesos 32 | 33 | #endif // SERENITY_MOCK_CONSUMER_HPP 34 | -------------------------------------------------------------------------------- /src/tests/common/mocks/mock_filter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MOCKFILTER_HPP 2 | #define SERENITY_MOCKFILTER_HPP 3 | 4 | #include 5 | 6 | #include "gmock/gmock.h" 7 | 8 | #include "serenity/serenity.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | template 15 | class MockFilter : public Producer

, 16 | public Consumer { 17 | public: 18 | MOCK_METHOD0(allProductsReady, void()); 19 | MOCK_METHOD1_T(consume, Try(const C&)); 20 | 21 | const std::vector&getConsumables() const { 22 | return Consumer::getConsumables(); 23 | } 24 | 25 | const Option getConsumable() const { 26 | return Consumer::getConsumable(); 27 | } 28 | 29 | void produce(P product) { 30 | Producer

::produce(product); 31 | } 32 | }; 33 | 34 | } // namespace tests 35 | } // namespace serenity 36 | } // namespace mesos 37 | 38 | #endif // SERENITY_MOCKFILTER_HPP 39 | -------------------------------------------------------------------------------- /src/tests/common/mocks/mock_multiple_consumer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MOCK_MULTIPLE_CONSUMER_HPP 2 | #define SERENITY_MOCK_MULTIPLE_CONSUMER_HPP 3 | 4 | #include 5 | 6 | #include "gmock/gmock.h" 7 | 8 | #include "serenity/serenity.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | template 15 | class MockMulitpleConsumer : public Consumer, 16 | public Consumer { 17 | public: 18 | MOCK_METHOD0(allProductsReady, void()); 19 | MOCK_METHOD1_T(consume, Try(const C1&)); 20 | MOCK_METHOD1_T(consume, Try(const C2&)); 21 | 22 | template 23 | const std::vector& getConsumables() const { 24 | return Consumer::getConsumables(); 25 | } 26 | 27 | template 28 | Option getConsumable() const { 29 | return Consumer::getConsumable(); 30 | } 31 | }; 32 | 33 | } // namespace tests 34 | } // namespace serenity 35 | } // namespace mesos 36 | 37 | #endif // SERENITY_MOCK_MULTIPLE_CONSUMER_HPP 38 | -------------------------------------------------------------------------------- /src/tests/common/serenity.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TESTS_SERENITY_HPP 2 | #define TESTS_SERENITY_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | namespace tests { 17 | 18 | 19 | #define DEFAULT_EXECUTOR_INFO \ 20 | ({ ExecutorInfo executor; \ 21 | executor.mutable_executor_id()->set_value("default"); \ 22 | executor.mutable_command()->set_value("exit 1"); \ 23 | executor; }) 24 | 25 | #define CREATE_EXECUTOR_INFO(executorId, command) \ 26 | ({ ExecutorInfo executor; \ 27 | executor.mutable_executor_id()->set_value(executorId); \ 28 | executor.mutable_command()->set_value(command); \ 29 | executor; }) 30 | 31 | 32 | // Factory for statistics stubs. 33 | class ResourceHelper { 34 | public: 35 | // TODO(bplotka) parametrize that 36 | static ResourceStatistics createStatistics() { 37 | ResourceStatistics statistics; 38 | statistics.set_cpus_nr_periods(100); 39 | statistics.set_cpus_nr_throttled(2); 40 | statistics.set_cpus_user_time_secs(4); 41 | statistics.set_cpus_system_time_secs(1); 42 | statistics.set_cpus_throttled_time_secs(0.5); 43 | statistics.set_cpus_limit(1.0); 44 | statistics.set_mem_file_bytes(0); 45 | statistics.set_mem_anon_bytes(0); 46 | statistics.set_mem_mapped_file_bytes(0); 47 | statistics.set_mem_rss_bytes(1024); 48 | statistics.set_mem_limit_bytes(2048); 49 | statistics.set_timestamp(0); 50 | 51 | return statistics; 52 | } 53 | 54 | static void addExecutor( 55 | ResourceUsage& usage, 56 | ExecutorInfo executorInfo, 57 | Resources allocated, 58 | ResourceStatistics statistics) { 59 | ResourceUsage::Executor* executor = usage.add_executors(); 60 | executor->mutable_executor_info()->CopyFrom(executorInfo); 61 | executor->mutable_allocated()->CopyFrom(allocated); 62 | executor->mutable_statistics()->CopyFrom(statistics); 63 | } 64 | }; 65 | 66 | 67 | // Fake usage function (same method as in mesos::slave::Slave). 68 | class MockSlaveUsage { 69 | public: 70 | MockSlaveUsage(int executors) : results(ResourceUsage()) { 71 | for (int i = 0; i < executors; i++) { 72 | ResourceHelper::addExecutor( 73 | results, 74 | CREATE_EXECUTOR_INFO(std::to_string(i + 1), "exit 1"), 75 | Resources(), 76 | ResourceHelper::createStatistics()); 77 | } 78 | } 79 | 80 | process::Future usage() { 81 | return results; 82 | } 83 | 84 | private: 85 | ResourceUsage results; 86 | }; 87 | 88 | } // namespace tests 89 | } // namespace serenity 90 | } // namespace mesos 91 | 92 | #endif // TESTS_SERENITY_HPP 93 | -------------------------------------------------------------------------------- /src/tests/common/signal_helper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_TEST_SIGNAL_HELPER_HPP 2 | #define SERENITY_TEST_SIGNAL_HELPER_HPP 3 | 4 | namespace mesos { 5 | namespace serenity { 6 | namespace tests { 7 | 8 | /** 9 | * This function fills instructions and cycles perf events to match given 10 | * IPC value. 11 | */ 12 | inline ResourceUsage_Executor generateIPC( 13 | const ResourceUsage_Executor _executorUsage, 14 | double_t _IpcValue, 15 | double_t _timestamp) { 16 | const int ACCURACY_MODIFIER = 10; 17 | // This function generates IPC with 1/ACCURACY_MODIFIER accuracy. 18 | _IpcValue *= ACCURACY_MODIFIER; 19 | ResourceUsage_Executor executorUsage; 20 | executorUsage.CopyFrom(_executorUsage); 21 | 22 | executorUsage.mutable_statistics() 23 | ->mutable_perf()->set_instructions(_IpcValue); 24 | 25 | executorUsage.mutable_statistics() 26 | ->mutable_perf()->set_cycles(ACCURACY_MODIFIER); 27 | 28 | executorUsage.mutable_statistics() 29 | ->mutable_perf()->set_timestamp(_timestamp); 30 | 31 | return executorUsage; 32 | } 33 | 34 | 35 | /** 36 | * This function fills instructions perf events to match given 37 | * IPC value. 38 | */ 39 | inline ResourceUsage_Executor generateIPS( 40 | const ResourceUsage_Executor _executorUsage, 41 | double_t _IpsValue, 42 | double_t _timestamp) { 43 | const int ACCURACY_MODIFIER = 10; 44 | // This function generates IPS with 1/ACCURACY_MODIFIER accuracy. 45 | _IpsValue *= ACCURACY_MODIFIER; 46 | ResourceUsage_Executor executorUsage; 47 | executorUsage.CopyFrom(_executorUsage); 48 | 49 | executorUsage.mutable_statistics() 50 | ->mutable_perf()->set_instructions(_IpsValue); 51 | 52 | executorUsage.mutable_statistics() 53 | ->mutable_perf()->set_duration(ACCURACY_MODIFIER); 54 | 55 | executorUsage.mutable_statistics() 56 | ->mutable_perf()->set_timestamp(_timestamp); 57 | 58 | return executorUsage; 59 | } 60 | 61 | 62 | /** 63 | * This function fills statistics cpu system time to match given 64 | * CPU value. 65 | */ 66 | inline ResourceUsage_Executor generateCpuUsage( 67 | const ResourceUsage_Executor _executorUsage, 68 | uint64_t _cumulativeCpuTimeValue, 69 | double_t _timestamp) { 70 | ResourceUsage_Executor executorUsage; 71 | executorUsage.CopyFrom(_executorUsage); 72 | 73 | executorUsage.mutable_statistics() 74 | ->set_cpus_system_time_secs(_cumulativeCpuTimeValue); 75 | 76 | executorUsage.mutable_statistics() 77 | ->set_cpus_user_time_secs(0); 78 | 79 | executorUsage.mutable_statistics() 80 | ->set_timestamp(_timestamp); 81 | 82 | return executorUsage; 83 | } 84 | 85 | } // namespace tests 86 | } // namespace serenity 87 | } // namespace mesos 88 | 89 | #endif // SERENITY_TEST_SIGNAL_HELPER_HPP 90 | -------------------------------------------------------------------------------- /src/tests/common/sinks/dummy_sink.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_DUMMY_SINK_HPP 2 | #define SERENITY_DUMMY_SINK_HPP 3 | 4 | #include 5 | 6 | #include "serenity/serenity.hpp" 7 | 8 | namespace mesos { 9 | namespace serenity { 10 | namespace tests { 11 | 12 | template 13 | class DummySink : public Consumer { 14 | public: 15 | DummySink() : numberOfMessagesConsumed(0) {} 16 | 17 | Try consume(const T& in) { 18 | this->numberOfMessagesConsumed++; 19 | return Nothing(); 20 | } 21 | 22 | uint32_t numberOfMessagesConsumed; 23 | }; 24 | 25 | } // namespace tests 26 | } // namespace serenity 27 | } // namespace mesos 28 | 29 | #endif // SERENITY_DUMMY_SINK_HPP 30 | -------------------------------------------------------------------------------- /src/tests/common/sinks/printer_sink.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_PRINTER_SINK_HPP 2 | #define SERENITY_PRINTER_SINK_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "serenity/serenity.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | template 15 | class PrinterSink : public Consumer { 16 | public: 17 | PrinterSink() : numberOfMessagesConsumed(0) {} 18 | 19 | Try consume(const T& in) override { 20 | JSON::Protobuf buffer(in); 21 | std::cout << "Msg #" << this->numberOfMessagesConsumed << std::endl 22 | << buffer << std::endl; 23 | 24 | this->numberOfMessagesConsumed++; 25 | return Nothing(); 26 | } 27 | uint32_t numberOfMessagesConsumed; 28 | }; 29 | 30 | } // namespace tests 31 | } // namespace serenity 32 | } // namespace mesos 33 | 34 | #endif // SERENITY_PRINTER_SINK_HPP 35 | -------------------------------------------------------------------------------- /src/tests/common/sources/json_source.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "tests/common/sources/json_source.hpp" 10 | 11 | #include "json_source.pb.h" // NOLINT(build/include) 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | namespace tests { 16 | 17 | Try JsonSource::RunTests(const std::string& jsonSource) { 18 | Try usages = JsonSource::ReadJson(jsonSource); 19 | if (usages.isError()) { 20 | LOG(ERROR) << "JsonSource failed: " << usages.error() << std::endl; 21 | } 22 | 23 | for (auto itr = usages.get().resource_usage().begin(); 24 | itr != usages.get().resource_usage().end(); 25 | itr++) { 26 | Try ret = produce(*itr); 27 | 28 | // Stop the pipeline in case of error. 29 | if (ret.isError()) return ret; 30 | } 31 | 32 | return Nothing(); 33 | } 34 | 35 | const Try JsonSource::ReadJson( 36 | const std::string& relativePath) { 37 | Try content = os::read(relativePath); 38 | if (content.isError()) { 39 | return Error("Read error: " + content.error()); 40 | } else if (!content.isSome()) { 41 | return Error("Readed file is none"); 42 | } 43 | 44 | std::string err; 45 | FixtureResourceUsage usages; 46 | int reply = pbjson::json2pb(content.get(), &usages, err); 47 | if (reply != 0) { 48 | Try emsg = strings::format( 49 | "Error during json deserialization| errno: %d | err: %s", reply, err); 50 | return Error(emsg.get()); 51 | } 52 | 53 | return usages; 54 | } 55 | 56 | } // namespace tests 57 | } // namespace serenity 58 | } // namespace mesos 59 | 60 | -------------------------------------------------------------------------------- /src/tests/common/sources/json_source.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_JSON_SOURCE_HPP 2 | #define SERENITY_JSON_SOURCE_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "json_source.pb.h" // NOLINT(build/include) 13 | 14 | #include "serenity/serenity.hpp" 15 | 16 | namespace mesos { 17 | namespace serenity { 18 | namespace tests { 19 | 20 | 21 | class JsonSource : public Producer { 22 | public: 23 | JsonSource() {} 24 | 25 | explicit JsonSource(Consumer* _consumer) { 26 | addConsumer(_consumer); 27 | } 28 | 29 | Try RunTests(const std::string& jsonSource); 30 | 31 | protected: 32 | static const Try ReadJson( 33 | const std::string& relativePath); 34 | }; 35 | 36 | } // namespace tests 37 | } // namespace serenity 38 | } // namespace mesos 39 | 40 | 41 | #endif // SERENITY_JSON_SOURCE_HPP 42 | -------------------------------------------------------------------------------- /src/tests/common/sources/json_source.proto: -------------------------------------------------------------------------------- 1 | 2 | import "mesos/mesos.proto"; 3 | 4 | package mesos; 5 | 6 | /** 7 | * Contains resource usage for running executors 8 | */ 9 | message FixtureResourceUsage { 10 | repeated ResourceUsage resource_usage = 1; 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/common/sources/mock_source.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_MOCK_SOURCE_HPP 2 | #define SERENITY_MOCK_SOURCE_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "serenity/serenity.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | template 15 | class MockSource : public Producer { 16 | public: 17 | MockSource() {} 18 | 19 | explicit MockSource(Consumer* _consumer) { 20 | Producer::addConsumer(_consumer); 21 | } 22 | 23 | explicit MockSource(Consumer* _consumer, 24 | Consumer* _consumer2) { 25 | Producer::addConsumer(_consumer); 26 | Producer::addConsumer(_consumer2); 27 | } 28 | 29 | Try produce(T out) { 30 | Producer::produce(out); 31 | 32 | return Nothing(); 33 | } 34 | }; 35 | 36 | } // namespace tests 37 | } // namespace serenity 38 | } // namespace mesos 39 | 40 | 41 | #endif // SERENITY_MOCK_SOURCE_HPP 42 | -------------------------------------------------------------------------------- /src/tests/common/usage_helper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_TESTS_USAGE_HELPER_HPP 2 | #define SERENITY_TESTS_USAGE_HELPER_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include "json_source.pb.h" // NOLINT(build/include) 18 | 19 | namespace mesos { 20 | namespace serenity { 21 | namespace tests { 22 | 23 | const std::string SERENITY_FIXTURES_DIR = "tests/fixtures/"; 24 | 25 | class JsonUsage { 26 | public: 27 | static const Try ReadJson( 28 | const std::string& relativePath) { 29 | Try content = os::read(relativePath); 30 | if (content.isError()) { 31 | return Error("Read error: " + content.error()); 32 | } else if (!content.isSome()) { 33 | return Error("Readed file is none"); 34 | } 35 | 36 | std::string err; 37 | FixtureResourceUsage usages; 38 | int reply = pbjson::json2pb(content.get(), &usages, err); 39 | if (reply != 0) { 40 | Try emsg = strings::format( 41 | "Error during json deserialization | errno: %d | err: %s", 42 | reply, 43 | err); 44 | return Error(emsg.get()); 45 | } 46 | 47 | return usages; 48 | } 49 | }; 50 | 51 | 52 | /** 53 | * Fake usage function (same method as in mesos::slave::Slave). 54 | * For internal unit tests use JsonSource. 55 | */ 56 | class MockSlaveUsage { 57 | public: 58 | explicit MockSlaveUsage(const std::string& jsonSource) { 59 | Try usages = JsonUsage::ReadJson(jsonSource); 60 | if (usages.isError()) { 61 | LOG(ERROR) << "Json Usage failed: " << usages.error() << std::endl; 62 | } 63 | 64 | results = usages.get(); 65 | } 66 | 67 | process::Future usage() { 68 | if (iteration >= results.resource_usage_size()) 69 | return ResourceUsage(); 70 | std::cout<< iteration << std::endl; 71 | return results.resource_usage(iteration++); 72 | } 73 | 74 | process::Future usageIter(int _iteration) { 75 | if (_iteration >= results.resource_usage_size()) 76 | return ResourceUsage(); 77 | std::cout<< _iteration << std::endl; 78 | return results.resource_usage(_iteration); 79 | } 80 | 81 | private: 82 | mesos::FixtureResourceUsage results; 83 | int iteration = 0; 84 | }; 85 | 86 | } // namespace tests 87 | } // namespace serenity 88 | } // namespace mesos 89 | 90 | #endif // SERENITY_TESTS_USAGE_HELPER_HPP 91 | -------------------------------------------------------------------------------- /src/tests/filters/correction_merger_test.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | 3 | #include "filters/correction_merger.hpp" 4 | 5 | #include "messages/serenity.hpp" 6 | 7 | #include "tests/common/mocks/mock_sink.hpp" 8 | #include "tests/common/mocks/mock_filter.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | /** 15 | * Expect CorrectionMerger to filter out duplicates and pass merged corrections. 16 | */ 17 | TEST(CorrectonMergerTest, MergingCorrections) { 18 | MockSink mockSink; 19 | MockFilter producer; 20 | CorrectionMergerFilter correctionMerger(&mockSink); 21 | 22 | producer.addConsumer(&correctionMerger); 23 | QoSCorrections corrections; 24 | 25 | // First correction. 26 | ExecutorInfo executorInfo; 27 | executorInfo.mutable_framework_id()->set_value("Framework1"); 28 | executorInfo.mutable_executor_id()->set_value("Executor1"); 29 | corrections.push_back(createKillQoSCorrection(createKill(executorInfo))); 30 | 31 | // Duplicated first correction. 32 | executorInfo.mutable_framework_id()->set_value("Framework1"); 33 | executorInfo.mutable_executor_id()->set_value("Executor1"); 34 | corrections.push_back(createKillQoSCorrection(createKill(executorInfo))); 35 | 36 | // Second correction (from the same framework). 37 | executorInfo.mutable_framework_id()->set_value("Framework1"); 38 | executorInfo.mutable_executor_id()->set_value("Executor2"); 39 | corrections.push_back(createKillQoSCorrection(createKill(executorInfo))); 40 | 41 | // Third correction. 42 | executorInfo.mutable_framework_id()->set_value("Framework2"); 43 | executorInfo.mutable_executor_id()->set_value("Executor1"); 44 | corrections.push_back(createKillQoSCorrection(createKill(executorInfo))); 45 | 46 | producer.produce(corrections); 47 | 48 | EXPECT_EQ(1, mockSink.numberOfMessagesConsumed); 49 | EXPECT_EQ(3, mockSink.currentConsumedT.size()); 50 | } 51 | 52 | } // namespace tests 53 | } // namespace serenity 54 | } // namespace mesos 55 | -------------------------------------------------------------------------------- /src/tests/filters/ignore_new_executors_test.cpp: -------------------------------------------------------------------------------- 1 | #include "filters/ignore_new_executors.hpp" 2 | 3 | #include "gmock/gmock.h" 4 | 5 | #include "tests/common/serenity.hpp" 6 | #include "tests/common/sinks/dummy_sink.hpp" 7 | #include "tests/common/sources/json_source.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | namespace tests { 12 | 13 | using testing::Return; 14 | using testing::Sequence; 15 | 16 | constexpr int IGNORE_NEW_EXECUTORS_SAMPLES = 6; 17 | 18 | 19 | class MockIgnoreNewExecutorsFilter : public IgnoreNewExecutorsFilter { 20 | public: 21 | MockIgnoreNewExecutorsFilter( 22 | Consumer* _consumer, 23 | uint32_t _threshold = 5 * 60) : 24 | IgnoreNewExecutorsFilter(_consumer, _threshold) {} 25 | 26 | MOCK_METHOD1(GetTime, time_t(time_t* arg)); 27 | }; 28 | 29 | 30 | /** 31 | * Time is mocked to be one second after begining of an Epoch 32 | */ 33 | TEST(IgnoreNewExecutorsFilter, IgnoreAllExecutors) { 34 | DummySink dummySink; 35 | MockIgnoreNewExecutorsFilter filter(&dummySink, 200); 36 | JsonSource jsonSource(&filter); 37 | 38 | EXPECT_CALL(filter, GetTime(nullptr)) 39 | .Times(4) 40 | .WillRepeatedly(Return(1)); 41 | 42 | jsonSource.RunTests("tests/fixtures/" 43 | "baseline_smoke_test_resource_usage.json"); 44 | 45 | ASSERT_EQ(dummySink.numberOfMessagesConsumed, 0); 46 | } 47 | 48 | 49 | /** 50 | * Time is mocked to be one second before end of an Epoch 51 | */ 52 | TEST(IgnoreNewExecutorsFilter, PassAllExecutors) { 53 | DummySink dummySink; 54 | MockIgnoreNewExecutorsFilter filter(&dummySink, 0); 55 | JsonSource jsonSource(&filter); 56 | 57 | EXPECT_CALL(filter, GetTime(nullptr)) 58 | .Times(4) 59 | .WillRepeatedly(Return(2147483647)); 60 | 61 | jsonSource.RunTests("tests/fixtures/" 62 | "baseline_smoke_test_resource_usage.json"); 63 | 64 | ASSERT_EQ(dummySink.numberOfMessagesConsumed, 4); 65 | } 66 | 67 | 68 | /** 69 | * Time flows from 0 to 5 70 | * Filter argument is 0 71 | */ 72 | TEST(IgnoreNewExecutorsFilter, PassFiveExecutors) { 73 | DummySink dummySink; 74 | MockIgnoreNewExecutorsFilter filter(&dummySink, 0); 75 | JsonSource jsonSource(&filter); 76 | 77 | Sequence s1; 78 | for (int idx = 0; idx < IGNORE_NEW_EXECUTORS_SAMPLES; ++idx) { 79 | EXPECT_CALL(filter, GetTime(nullptr)) 80 | .InSequence(s1) 81 | .WillOnce(Return(idx)); 82 | } 83 | 84 | jsonSource.RunTests("tests/fixtures/ignore_new_executors.json"); 85 | 86 | ASSERT_EQ(dummySink.numberOfMessagesConsumed, 5); 87 | } 88 | 89 | 90 | /** 91 | * Time flows from 0 to 5 92 | * Filter argument is 3 93 | */ 94 | TEST(IgnoreNewExecutorsFilter, PassTwoExecutors) { 95 | DummySink dummySink; 96 | MockIgnoreNewExecutorsFilter filter(&dummySink, 3); 97 | JsonSource jsonSource(&filter); 98 | 99 | Sequence s1; 100 | for (int idx = 0; idx < IGNORE_NEW_EXECUTORS_SAMPLES; ++idx) { 101 | EXPECT_CALL(filter, GetTime(nullptr)) 102 | .InSequence(s1) 103 | .WillOnce(Return(idx)); 104 | } 105 | 106 | jsonSource.RunTests("tests/fixtures/ignore_new_executors.json"); 107 | 108 | ASSERT_EQ(dummySink.numberOfMessagesConsumed, 2); 109 | } 110 | 111 | } // namespace tests 112 | } // namespace serenity 113 | } // namespace mesos 114 | -------------------------------------------------------------------------------- /src/tests/filters/pr_executor_pass_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "filters/pr_executor_pass.hpp" 8 | 9 | #include "process/future.hpp" 10 | 11 | #include "tests/common/mocks/mock_sink.hpp" 12 | #include "tests/common/sources/json_source.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | namespace tests { 17 | 18 | using ::testing::DoAll; 19 | 20 | TEST(PrTasksFilterTest, BeTasksFilteredOut) { 21 | // End of pipeline. 22 | MockSink mockSink; 23 | process::Future usage; 24 | EXPECT_CALL(mockSink, consume(_)) 25 | .WillOnce(DoAll( 26 | FutureArg<0>(&usage), 27 | Return(Nothing()))); 28 | 29 | // Second component in pipeline. 30 | PrExecutorPassFilter prTasksFilter(&mockSink); 31 | 32 | // First component in pipeline. 33 | JsonSource jsonSource(&prTasksFilter); 34 | 35 | // Start test. 36 | ASSERT_SOME(jsonSource.RunTests("tests/fixtures/pr_executor_pass/test.json")); 37 | 38 | ASSERT_TRUE(usage.isReady()); 39 | ASSERT_EQ(1u, usage.get().executors().size()); 40 | 41 | Resources allocated(usage.get().executors(0).allocated()); 42 | EXPECT_TRUE(allocated.revocable().empty()); 43 | } 44 | 45 | 46 | TEST(PrTasksFilterTest, NoExecutorUsage) { 47 | // End of pipeline. 48 | MockSink mockSink; 49 | process::Future usage; 50 | EXPECT_CALL(mockSink, consume(_)) 51 | .WillOnce(DoAll( 52 | FutureArg<0>(&usage), 53 | Return(Nothing()))); 54 | 55 | // Second component in pipeline. 56 | PrExecutorPassFilter prTasksFilter(&mockSink); 57 | 58 | // First component in pipeline. 59 | JsonSource jsonSource(&prTasksFilter); 60 | 61 | // Start test. 62 | ASSERT_SOME(jsonSource.RunTests( 63 | "tests/fixtures/pr_executor_pass/insufficient_metrics_test.json")); 64 | 65 | ASSERT_TRUE(usage.isReady()); 66 | 67 | // No executor passed. 68 | ASSERT_EQ(0, usage.get().executors().size()); 69 | } 70 | 71 | } // namespace tests 72 | } // namespace serenity 73 | } // namespace mesos 74 | 75 | -------------------------------------------------------------------------------- /src/tests/filters/utilization_threshold_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "filters/utilization_threshold.hpp" 8 | 9 | #include "process/future.hpp" 10 | 11 | #include "tests/common/mocks/mock_sink.hpp" 12 | #include "tests/common/sources/json_source.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | namespace tests { 17 | 18 | using ::testing::DoAll; 19 | 20 | 21 | TEST(UtilizationThresholdFilterTest, OkLoad) { 22 | // End of pipeline. 23 | MockSink mockSink; 24 | process::Future usage; 25 | EXPECT_CALL(mockSink, consume(_)) 26 | .WillOnce(InvokeConsumeUsageCountExecutors(&mockSink, 1)); 27 | 28 | // Second component in pipeline. 29 | UtilizationThresholdFilter utilizationFilter(&mockSink); 30 | 31 | // First component in pipeline. 32 | JsonSource jsonSource(&utilizationFilter); 33 | 34 | // Start test. 35 | // This fixture includes three pipeline loops: 36 | // - ResourceUsage with cpu statistics included. 37 | // First loop so allocated resources will be used. 38 | // allocated ~== total so oversubscription will be blocked. 39 | // - ResourceUsage with cpu statistics included. 40 | // cpu_usage < total so oversubscription will be passed. 41 | // - ResourceUsage without cpu statistics included. 42 | // In that case allocated resources will be used. 43 | // allocated ~== total so oversubscription will be blocked. 44 | ASSERT_SOME(jsonSource.RunTests( 45 | "tests/fixtures/utilization_threshold/ok_load_test.json")); 46 | } 47 | 48 | 49 | TEST(UtilizationThresholdFilterTest, TooHighLoad) { 50 | // End of pipeline. 51 | MockSink mockSink; 52 | EXPECT_CALL(mockSink, consume(_)) 53 | .Times(0); 54 | 55 | // Second component in pipeline. 56 | UtilizationThresholdFilter utilizationFilter(&mockSink); 57 | 58 | // First component in pipeline. 59 | JsonSource jsonSource(&utilizationFilter); 60 | 61 | // Start test. 62 | // This fixture includes two pipeline loops: 63 | // - ResourceUsage with cpu statistics included. 64 | // First loop so allocated resources will be used. 65 | // allocated ~== total so oversubscription will be blocked. 66 | // - ResourceUsage with cpu statistics included. 67 | // cpu_usage ~== total so oversubscription will be blocked. 68 | ASSERT_SOME(jsonSource.RunTests( 69 | "tests/fixtures/utilization_threshold/too_high_load_test.json")); 70 | } 71 | 72 | } // namespace tests 73 | } // namespace serenity 74 | } // namespace mesos 75 | 76 | -------------------------------------------------------------------------------- /src/tests/fixtures/be_start_json_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource_usage": [ 3 | { 4 | "total": [ 5 | { 6 | "name": "cpus", 7 | "role": "*", 8 | "scalar": { 9 | "value": 8 10 | }, 11 | "type": "SCALAR" 12 | }, 13 | { 14 | "name": "mem", 15 | "role": "*", 16 | "scalar": { 17 | "value": 4049 18 | }, 19 | "type": "SCALAR" 20 | } 21 | ], 22 | "executors": [ 23 | { 24 | "allocated": [ 25 | { 26 | "revocable": {}, 27 | "name": "cpus", 28 | "role": "*", 29 | "scalar": { 30 | "value": 1.1 31 | }, 32 | "type": "SCALAR" 33 | }, 34 | { 35 | "name": "mem", 36 | "role": "*", 37 | "scalar": { 38 | "value": 160 39 | }, 40 | "type": "SCALAR" 41 | } 42 | ], 43 | "executor_info": { 44 | "command": { 45 | "shell": true, 46 | "value": "/home/skonefal/usr/libexec/mesos/mesos-executor" 47 | }, 48 | "executor_id": { 49 | "value": "serenity2" 50 | }, 51 | "framework_id": { 52 | "value": "20150612-182512-16842879-5050-6428-0001" 53 | }, 54 | "name": "Command Executor (Task: test1) (Command: sh -c 'watch pwd')", 55 | "resources": [ 56 | { 57 | "name": "cpus", 58 | "role": "*", 59 | "scalar": { 60 | "value": 0.1 61 | }, 62 | "type": "SCALAR" 63 | }, 64 | { 65 | "name": "mem", 66 | "role": "*", 67 | "scalar": { 68 | "value": 32 69 | }, 70 | "type": "SCALAR" 71 | } 72 | ], 73 | "source": "serenity2" 74 | }, 75 | "statistics": { 76 | "cpus_limit": 1.1, 77 | "cpus_system_time_secs": 0.02, 78 | "cpus_user_time_secs": 0.07, 79 | "mem_limit_bytes": 167772160, 80 | "mem_rss_bytes": 70057984, 81 | "perf": { 82 | "cycles": 48717706, 83 | "instructions": 13838911, 84 | "timestamp": 1434126327.20772 85 | }, 86 | "timestamp": 1434126327.18446 87 | } 88 | } 89 | ] 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /src/tests/fixtures/ema/insufficient_metrics_test.json: -------------------------------------------------------------------------------- 1 | {"resource_usage":[{"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150612-182512-16842879-5050-6428-0001"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":1.1,"cpus_user_time_secs":0.07,"mem_limit_bytes":167772160,"mem_rss_bytes":70057984,"timestamp":1434126327.18446}},{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"test1"},"framework_id":{"value":"20150612-182512-16842879-5050-6428-0000"},"name":"Command Executor (Task: test1) (Command: sh -c 'watch ls')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"test1"},"statistics":{"cpus_limit":1.1,"cpus_user_time_secs":0.1,"mem_limit_bytes":167772160,"mem_rss_bytes":71229440,"timestamp":1434126327.20772}}]},{"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150612-182512-16842879-5050-6428-0001"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":1.1,"mem_limit_bytes":167772160,"mem_rss_bytes":70057984,"timestamp":1434126327.18446}},{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"test1"},"framework_id":{"value":"20150612-182512-16842879-5050-6428-0000"},"name":"Command Executor (Task: test1) (Command: sh -c 'watch ls')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"test1"}}]}]} -------------------------------------------------------------------------------- /src/tests/fixtures/pr_executor_pass/insufficient_metrics_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource_usage": [ 3 | { 4 | "executors": [ 5 | { 6 | "allocated": [], 7 | "executor_info": { 8 | "command": { 9 | "shell": true, 10 | "value": "/home/skonefal/usr/libexec/mesos/mesos-executor" 11 | }, 12 | "executor_id": { 13 | "value": "serenity2" 14 | }, 15 | "framework_id": { 16 | "value": "20150612-182512-16842879-5050-6428-0001" 17 | }, 18 | "name": "Command Executor (Task: test1) (Command: sh -c 'watch pwd')", 19 | "resources": [ 20 | { 21 | "name": "cpus", 22 | "role": "*", 23 | "scalar": { 24 | "value": 0.1 25 | }, 26 | "type": "SCALAR" 27 | }, 28 | { 29 | "name": "mem", 30 | "role": "*", 31 | "scalar": { 32 | "value": 32 33 | }, 34 | "type": "SCALAR" 35 | } 36 | ], 37 | "source": "serenity2" 38 | }, 39 | "statistics": { 40 | "cpus_limit": 1.1, 41 | "cpus_system_time_secs": 0.03, 42 | "cpus_user_time_secs": 0.07, 43 | "mem_limit_bytes": 167772160, 44 | "mem_rss_bytes": 70057984, 45 | "perf": { 46 | "cycles": 48717706, 47 | "instructions": 13838911, 48 | "timestamp": 1434126327.20772 49 | }, 50 | "timestamp": 1434126327.18446 51 | } 52 | }, 53 | { 54 | "allocated": [], 55 | "executor_info": { 56 | "command": { 57 | "shell": true, 58 | "value": "/home/skonefal/usr/libexec/mesos/mesos-executor" 59 | }, 60 | "executor_id": { 61 | "value": "test1" 62 | }, 63 | "framework_id": { 64 | "value": "20150612-182512-16842879-5050-6428-0000" 65 | }, 66 | "name": "Command Executor (Task: test2) (Command: sh -c 'watch ls')", 67 | "resources": [ 68 | { 69 | "name": "cpus", 70 | "role": "*", 71 | "scalar": { 72 | "value": 0.1 73 | }, 74 | "type": "SCALAR" 75 | }, 76 | { 77 | "name": "mem", 78 | "role": "*", 79 | "scalar": { 80 | "value": 32 81 | }, 82 | "type": "SCALAR" 83 | } 84 | ], 85 | "source": "test1" 86 | } 87 | } 88 | ] 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /src/tests/fixtures/slack_estimator/max_oversubscription_fraction_test.json: -------------------------------------------------------------------------------- 1 | {"resource_usage": 2 | [{"total": [{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"}, 3 | {"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}], 4 | "executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"}, 5 | {"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}], 6 | "executor_info": 7 | {"command":{"shell":true,"value":"/home/mesos/usr/libexec/mesos/mesos-executor"}, 8 | "executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"}, 9 | "name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')", 10 | "resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"}, 11 | {"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"}, "statistics": 12 | {"cpus_limit":4,"cpus_system_time_secs":0.0, "cpus_user_time_secs":0.0, "mem_limit_bytes":167772160, "mem_rss_bytes":70983680, "timestamp":1}}]}, 13 | {"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"}, 14 | {"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}], 15 | "executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"}, 16 | {"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}], 17 | "executor_info": 18 | {"command":{"shell":true,"value":"/home/mesos/usr/libexec/mesos/mesos-executor"}, 19 | "executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"}, 20 | "name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')", 21 | "resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"}, 22 | {"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"}, 23 | "statistics":{"cpus_limit":4,"cpus_system_time_secs":0.0,"cpus_user_time_secs":0.0,"mem_limit_bytes":167772160,"mem_rss_bytes":70983680,"timestamp":2}}]} 24 | ]} -------------------------------------------------------------------------------- /src/tests/fixtures/start_json_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource_usage": [ 3 | { 4 | "total": [ 5 | { 6 | "name": "cpus", 7 | "role": "*", 8 | "scalar": { 9 | "value": 8 10 | }, 11 | "type": "SCALAR" 12 | }, 13 | { 14 | "name": "mem", 15 | "role": "*", 16 | "scalar": { 17 | "value": 4049 18 | }, 19 | "type": "SCALAR" 20 | } 21 | ], 22 | "executors": [ 23 | { 24 | "allocated": [ 25 | { 26 | "name": "cpus", 27 | "role": "*", 28 | "scalar": { 29 | "value": 1.1 30 | }, 31 | "type": "SCALAR" 32 | }, 33 | { 34 | "name": "mem", 35 | "role": "*", 36 | "scalar": { 37 | "value": 160 38 | }, 39 | "type": "SCALAR" 40 | } 41 | ], 42 | "executor_info": { 43 | "command": { 44 | "shell": true, 45 | "value": "/home/skonefal/usr/libexec/mesos/mesos-executor" 46 | }, 47 | "executor_id": { 48 | "value": "serenity2" 49 | }, 50 | "framework_id": { 51 | "value": "20150612-182512-16842879-5050-6428-0001" 52 | }, 53 | "name": "Command Executor (Task: test1) (Command: sh -c 'watch pwd')", 54 | "resources": [ 55 | { 56 | "name": "cpus", 57 | "role": "*", 58 | "scalar": { 59 | "value": 0.1 60 | }, 61 | "type": "SCALAR" 62 | }, 63 | { 64 | "name": "mem", 65 | "role": "*", 66 | "scalar": { 67 | "value": 32 68 | }, 69 | "type": "SCALAR" 70 | } 71 | ], 72 | "source": "serenity2" 73 | }, 74 | "statistics": { 75 | "cpus_limit": 1.1, 76 | "cpus_system_time_secs": 0.02, 77 | "cpus_user_time_secs": 0.07, 78 | "mem_limit_bytes": 167772160, 79 | "mem_rss_bytes": 70057984, 80 | "perf": { 81 | "cycles": 48717706, 82 | "instructions": 13838911, 83 | "timestamp": 1434126327.20772 84 | }, 85 | "timestamp": 1434126327.18446 86 | } 87 | } 88 | ] 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /src/tests/fixtures/utilization_threshold/ok_load_test.json: -------------------------------------------------------------------------------- 1 | {"resource_usage":[{"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}],"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":4,"cpus_system_time_secs":0.2,"cpus_user_time_secs":0.5,"mem_limit_bytes":167772160,"mem_rss_bytes":70983680,"timestamp":1}}]},{"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}],"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":4,"cpus_system_time_secs":0.9,"cpus_user_time_secs":1.1,"mem_limit_bytes":167772160,"mem_rss_bytes":70983680,"timestamp":2}}]},{"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}],"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"}}]}]} -------------------------------------------------------------------------------- /src/tests/fixtures/utilization_threshold/too_high_load_test.json: -------------------------------------------------------------------------------- 1 | {"resource_usage":[{"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}],"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":4,"cpus_system_time_secs":0,"cpus_user_time_secs":0.1,"mem_limit_bytes":167772160,"mem_rss_bytes":70983680,"timestamp":1}}]},{"total":[{"name":"cpus","role":"*","scalar":{"value":2},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":300},"type":"SCALAR"}],"executors":[{"allocated":[{"name":"cpus","role":"*","scalar":{"value":1.9},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":160},"type":"SCALAR"}],"executor_info":{"command":{"shell":true,"value":"/home/skonefal/usr/libexec/mesos/mesos-executor"},"executor_id":{"value":"serenity2"},"framework_id":{"value":"20150622-161150-16842879-5050-16372-0000"},"name":"Command Executor (Task: serenity2) (Command: sh -c 'watch pwd')","resources":[{"name":"cpus","role":"*","scalar":{"value":0.1},"type":"SCALAR"},{"name":"mem","role":"*","scalar":{"value":32},"type":"SCALAR"}],"source":"serenity2"},"statistics":{"cpus_limit":4,"cpus_system_time_secs":1.2,"cpus_user_time_secs":0.8,"mem_limit_bytes":167772160,"mem_rss_bytes":70983680,"timestamp":2}}]}]} -------------------------------------------------------------------------------- /src/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "messages/serenity.hpp" 5 | 6 | int main(int argc, char** argv) { 7 | GOOGLE_PROTOBUF_VERIFY_VERSION; 8 | FLAGS_logtostderr = true; 9 | FLAGS_minloglevel = google::ERROR; 10 | 11 | ::testing::InitGoogleTest(&argc, argv); 12 | // Initialize Google's logging library. 13 | // Comment that for debug log (INFO lvl) 14 | google::InitGoogleLogging(argv[0]); 15 | 16 | return RUN_ALL_TESTS(); 17 | } 18 | -------------------------------------------------------------------------------- /src/tests/mesos_modules/qos_controller/qos_controller_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include "mesos/resources.hpp" 6 | 7 | #include "mesos/slave/oversubscription.hpp" 8 | #include "mesos/slave/qos_controller.hpp" 9 | 10 | #include "mesos_modules/qos_controller/serenity_controller.hpp" 11 | 12 | #include "process/clock.hpp" 13 | #include "process/gtest.hpp" 14 | 15 | #include "pipeline/qos_pipeline.hpp" 16 | 17 | #include "stout/gtest.hpp" 18 | 19 | #include "tests/common/usage_helper.hpp" 20 | 21 | using std::list; 22 | 23 | using mesos::slave::QoSController; 24 | using mesos::slave::QoSCorrection; 25 | 26 | namespace mesos { 27 | namespace serenity { 28 | namespace tests { 29 | 30 | class TestCorrectionPipeline : public QoSControllerPipeline { 31 | public: 32 | TestCorrectionPipeline() {} 33 | 34 | virtual Result run(const ResourceUsage& _product) { 35 | QoSCorrections corrections; 36 | 37 | ExecutorInfo executorInfo; 38 | executorInfo.mutable_framework_id()->set_value("Framework1"); 39 | executorInfo.mutable_executor_id()->set_value("Executor1"); 40 | 41 | QoSCorrection correction = 42 | createKillQoSCorrection(createKill(executorInfo)); 43 | corrections.push_back(correction); 44 | return corrections; 45 | } 46 | }; 47 | 48 | /** 49 | * This tests checks the interface. 50 | */ 51 | TEST(SerenityControllerTest, PipelineIntegration) { 52 | Try qoSController = 53 | serenity::SerenityController::create( 54 | std::shared_ptr( 55 | new TestCorrectionPipeline())); 56 | ASSERT_SOME(qoSController); 57 | 58 | QoSController* controller = qoSController.get(); 59 | 60 | MockSlaveUsage usage( 61 | "tests/fixtures/baseline_smoke_test_resource_usage.json"); 62 | 63 | Try initialize = controller->initialize( 64 | lambda::bind(&MockSlaveUsage::usage, &usage)); 65 | 66 | process::Future> result = controller->corrections(); 67 | 68 | AWAIT_READY(result); 69 | 70 | EXPECT_EQ(1u, result.get().size()); 71 | 72 | EXPECT_EQ("Executor1", result.get().front().kill().executor_id().value()); 73 | EXPECT_EQ("Framework1", result.get().front().kill().framework_id().value()); 74 | } 75 | 76 | } // namespace tests 77 | } // namespace serenity 78 | } // namespace mesos 79 | 80 | -------------------------------------------------------------------------------- /src/tests/mesos_modules/resource_estimator/estimator_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include "mesos/resources.hpp" 6 | 7 | #include "mesos/slave/resource_estimator.hpp" 8 | 9 | #include "mesos_modules/resource_estimator/serenity_estimator.hpp" 10 | 11 | #include "stout/gtest.hpp" 12 | 13 | #include "process/gtest.hpp" 14 | 15 | #include "tests/common/usage_helper.hpp" 16 | 17 | using std::list; 18 | 19 | using mesos::slave::ResourceEstimator; 20 | 21 | namespace mesos { 22 | namespace serenity { 23 | namespace tests { 24 | 25 | class TestEstimationPipeline : public ResourceEstimatorPipeline { 26 | public: 27 | TestEstimationPipeline() {} 28 | 29 | virtual Result run(const ResourceUsage& _product) { 30 | return Resources::parse("cpus(*):16"); 31 | } 32 | }; 33 | 34 | 35 | /** 36 | * This tests checks the interface. 37 | */ 38 | TEST(SerenityEstimatorTest, PipelineIntegration) { 39 | Try resourceEstimator = 40 | serenity::SerenityEstimator::create( 41 | std::shared_ptr( 42 | new TestEstimationPipeline())); 43 | ASSERT_SOME(resourceEstimator); 44 | 45 | ResourceEstimator* estimator = resourceEstimator.get(); 46 | 47 | MockSlaveUsage usage( 48 | "tests/fixtures/baseline_smoke_test_resource_usage.json"); 49 | 50 | Try initialize = estimator->initialize( 51 | lambda::bind(&MockSlaveUsage::usage, &usage)); 52 | 53 | process::Future result = estimator->oversubscribable(); 54 | 55 | AWAIT_READY(result); 56 | 57 | for (Resources slack : result.get()) { 58 | for (Resource slack_resource : slack) { 59 | EXPECT_EQ("cpus", slack_resource.name()); 60 | EXPECT_EQ(16, slack_resource.scalar().value()); 61 | } 62 | } 63 | } 64 | 65 | } // namespace tests 66 | } // namespace serenity 67 | } // namespace mesos 68 | 69 | -------------------------------------------------------------------------------- /src/tests/observers/qos_correction_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "gtest/gtest.h" 6 | #include "gmock/gmock.h" 7 | 8 | #include "stout/gtest.hpp" 9 | 10 | #include "mesos/mesos.hpp" 11 | 12 | #include "messages/serenity.hpp" 13 | 14 | #include "filters/executor_age.hpp" 15 | 16 | #include "observers/qos_correction.hpp" 17 | 18 | #include "serenity/resource_helper.hpp" 19 | #include "serenity/wid.hpp" 20 | 21 | #include "tests/common/usage_helper.hpp" 22 | #include "tests/common/sources/mock_source.hpp" 23 | 24 | namespace mesos { 25 | namespace serenity { 26 | namespace tests { 27 | 28 | using ::testing::DoAll; 29 | using std::string; 30 | 31 | // This fixture includes 5 executors: 32 | // - 1 BE <1 CPUS> id 0 33 | // - 2 BE <0.5 CPUS> id 1,2 34 | // - 1 PR <4 CPUS> id 3 35 | // - 1 PR <2 CPUS> id 4 36 | const char QOS_FIXTURE[] = "tests/fixtures/qos/average_usage.json"; 37 | const int BE_1CPUS = 0; 38 | const int BE_0_5CPUS_1 = 1; 39 | const int BE_0_5CPUS_2 = 2; 40 | const int PR_4CPUS = 3; 41 | const int PR_2CPUS = 4; 42 | 43 | class MockQosController : public QoSCorrectionObserver { 44 | public: 45 | MockQosController() : 46 | QoSCorrectionObserver(nullptr) {} 47 | 48 | MOCK_METHOD0(emptyContentionsReceived, void()); 49 | MOCK_METHOD0(doQosDecision, void()); 50 | }; 51 | 52 | class MockQosRevocationStrategy : public RevocationStrategy { 53 | public: 54 | MockQosRevocationStrategy() : 55 | RevocationStrategy(Tag(QOS_CONTROLLER, "Mocked")) {} 56 | 57 | MOCK_METHOD3(decide, Try(ExecutorAgeFilter* exeutorAge, 58 | const Contentions& contentions, 59 | const ResourceUsage& usage)); 60 | }; 61 | 62 | /** 63 | * Check if getRevocableExecutors function properly filters out PR executors. 64 | */ 65 | TEST(QosControllerTest, emptyContentionsReceived) { 66 | MockQosController qosController; 67 | MockQosRevocationStrategy qosStrategy; 68 | 69 | const ResourceUsage usage; 70 | std::vector syncContenions; 71 | 72 | // qosController.consume(usage); 73 | // qosController.consume(syncContenions); 74 | 75 | // EXPECT_CALL(qosController, doQosDecision()); 76 | // EXPECT_CALL(qosController, emptyContentionsReceived()); 77 | } 78 | 79 | } // namespace tests 80 | } // namespace serenity 81 | } // namespace mesos 82 | -------------------------------------------------------------------------------- /src/tests/observers/slack_resource_test.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | 3 | #include "mesos/mesos.hpp" 4 | #include "mesos/resources.hpp" 5 | 6 | #include "observers/slack_resource.hpp" 7 | 8 | #include "stout/try.hpp" 9 | #include "stout/nothing.hpp" 10 | 11 | #include "serenity/math_utils.hpp" 12 | 13 | #include "tests/common/sources/json_source.hpp" 14 | #include "tests/common/mocks/mock_sink.hpp" 15 | 16 | namespace mesos { 17 | namespace serenity { 18 | namespace tests { 19 | 20 | using ::testing::DoubleEq; 21 | using ::testing::Eq; 22 | using ::testing::InSequence; 23 | using ::testing::_; 24 | 25 | ACTION_P(BasicTestAction, check) { 26 | Resources resources = arg0; 27 | 28 | if (utils::AlmostZero(check)) { 29 | EXPECT_TRUE(resources.empty()); 30 | } else { 31 | EXPECT_FALSE(resources.empty()); 32 | 33 | // if is used here, because gmock can not use ASSERT inside ACTION 34 | if (!resources.empty()) { 35 | EXPECT_THAT(resources.begin()->scalar().value(), DoubleEq(check)); 36 | EXPECT_THAT(++resources.begin(), Eq(resources.end())); 37 | } 38 | } 39 | return Nothing(); 40 | } 41 | 42 | 43 | TEST(SlackResourceObserver, BasicTest) { 44 | SlackResourceObserver observer; 45 | JsonSource jsonSource; 46 | MockSink mockSink; 47 | 48 | jsonSource.addConsumer(&observer); 49 | observer.addConsumer(&mockSink); 50 | 51 | std::array expectedCpus = {0, 0, 2, 4, 1, 3.7}; 52 | 53 | { 54 | InSequence seq; 55 | for (double_t param : expectedCpus) { 56 | EXPECT_CALL(mockSink, consume(_)).WillOnce(BasicTestAction(param)); 57 | } 58 | } 59 | jsonSource.RunTests( 60 | "tests/fixtures/slack_estimator/slack_calculation_test.json"); 61 | } 62 | 63 | 64 | TEST(SlackResourceObserver, MaxOversubscriptionFraction) { 65 | SlackResourceObserver observer; 66 | JsonSource jsonSource; 67 | MockSink mockSink; 68 | 69 | jsonSource.addConsumer(&observer); 70 | observer.addConsumer(&mockSink); 71 | 72 | std::array expectedCpus = {0.0, 1.6}; 73 | 74 | { 75 | InSequence seq; 76 | for (double_t param : expectedCpus) { 77 | EXPECT_CALL(mockSink, consume(_)).WillOnce(BasicTestAction(param)); 78 | } 79 | } 80 | jsonSource.RunTests("tests/fixtures/slack_estimator/" 81 | "max_oversubscription_fraction_test.json"); 82 | } 83 | 84 | 85 | } // namespace tests 86 | } // namespace serenity 87 | } // namespace mesos 88 | -------------------------------------------------------------------------------- /src/tests/observers/strategies/cache_occupancy_strategy_test.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | 3 | #include "mesos/mesos.hpp" 4 | #include "mesos/resources.hpp" 5 | 6 | #include "observers/slack_resource.hpp" 7 | 8 | #include "serenity/math_utils.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | using ::testing::DoubleEq; 15 | using ::testing::Eq; 16 | using ::testing::InSequence; 17 | using ::testing::_; 18 | 19 | ACTION_P(BasicTestAction, check) { 20 | Resources resources = arg0; 21 | 22 | if (utils::AlmostZero(check)) { 23 | EXPECT_TRUE(resources.empty()); 24 | } else { 25 | EXPECT_FALSE(resources.empty()); 26 | 27 | // if is used here, because gmock can not use ASSERT inside ACTION 28 | if (!resources.empty()) { 29 | EXPECT_THAT(resources.begin()->scalar().value(), DoubleEq(check)); 30 | EXPECT_THAT(++resources.begin(), Eq(resources.end())); 31 | } 32 | } 33 | return Nothing(); 34 | } 35 | 36 | } // namespace tests 37 | } // namespace serenity 38 | } // namespace mesos 39 | -------------------------------------------------------------------------------- /src/tests/pipeline/estimator_pipeline_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include "mesos/resources.hpp" 6 | 7 | #include "process/clock.hpp" 8 | #include "process/gtest.hpp" 9 | 10 | #include "pipeline/estimator_pipeline.hpp" 11 | 12 | #include "stout/gtest.hpp" 13 | 14 | #include "tests/common/usage_helper.hpp" 15 | 16 | using std::list; 17 | 18 | namespace mesos { 19 | namespace serenity { 20 | namespace tests { 21 | 22 | TEST(EstimatorPipelineTest, FiltersNotProperlyFed) { 23 | Try usages = 24 | JsonUsage::ReadJson("tests/fixtures/pipeline/insufficient_metrics.json"); 25 | if (usages.isError()) { 26 | LOG(ERROR) << "JsonSource failed: " << usages.error() << std::endl; 27 | ASSERT_FALSE(usages.isError()); // test failure. 28 | } 29 | 30 | ResourceUsage usage; 31 | usage.CopyFrom(usages.get().resource_usage(0)); 32 | 33 | ResourceEstimatorPipeline* pipeline = new CpuEstimatorPipeline(); 34 | 35 | Result slack = pipeline->run(usage); 36 | EXPECT_NONE(slack); 37 | 38 | delete pipeline; 39 | } 40 | 41 | 42 | TEST(EstimatorPipelineTest, NoSlack) { 43 | Try usages = 44 | JsonUsage::ReadJson("tests/fixtures/pipeline/sufficient_metrics.json"); 45 | if (usages.isError()) { 46 | LOG(ERROR) << "JsonSource failed: " << usages.error() << std::endl; 47 | ASSERT_FALSE(usages.isError()); // test failure. 48 | } 49 | 50 | ResourceUsage usage; 51 | usage.CopyFrom(usages.get().resource_usage(0)); 52 | 53 | ResourceEstimatorPipeline* pipeline = new CpuEstimatorPipeline(); 54 | 55 | Result slack = pipeline->run(usage); 56 | ASSERT_SOME(slack); 57 | 58 | EXPECT_TRUE(slack.get().empty()); 59 | 60 | delete pipeline; 61 | } 62 | 63 | } // namespace tests 64 | } // namespace serenity 65 | } // namespace mesos 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/tests/serenity/agent_utils_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "mesos/mesos.hpp" 3 | 4 | #include "serenity/agent_utils.hpp" 5 | 6 | namespace mesos { 7 | namespace serenity { 8 | namespace tests { 9 | 10 | TEST(AgentUtilsTest, GetHostname) { 11 | auto result = AgentInfo::GetHostName(); 12 | ASSERT_TRUE(result.isSome()); 13 | } 14 | 15 | TEST(AgentUtilsTest, GetAgentId) { 16 | auto result = AgentInfo::GetAgentId(); 17 | ASSERT_TRUE(result.isSome()); 18 | } 19 | 20 | } // namespace tests 21 | } // namespace serenity 22 | } // namespace mesos 23 | 24 | -------------------------------------------------------------------------------- /src/tests/serenity/config_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | #include "serenity/config.hpp" 5 | 6 | #include "stout/gtest.hpp" 7 | 8 | #include "tests/common/config_helper.hpp" 9 | 10 | namespace mesos { 11 | namespace serenity { 12 | namespace tests { 13 | 14 | // TestConfig required fields & default values using different types. 15 | const constexpr char* FIELD_STR = "FIELD_STR"; 16 | const constexpr char* DEFAULT_FIELD_STR = "default"; 17 | const constexpr char* MODIFIED_FIELD_STR = "modified"; 18 | 19 | const constexpr char* FIELD_BOOL = "FIELD_BOOL"; 20 | const constexpr bool DEFAULT_FIELD_BOOL = true; 21 | const constexpr bool MODIFIED_FIELD_BOOL = false; 22 | 23 | const constexpr char* FIELD_UINT = "FIELD_UINT"; 24 | const constexpr uint64_t DEFAULT_FIELD_UINT = 23424; 25 | const constexpr uint64_t MODIFIED_FIELD_UINT = 3; 26 | 27 | const constexpr char* FIELD_INT = "FIELD_INT"; 28 | const constexpr int64_t DEFAULT_FIELD_INT = -435; 29 | const constexpr int64_t MODIFIED_FIELD_INT = 34535; 30 | 31 | const constexpr char* FIELD_DOUBLE = "FIELD_DOUBLE"; 32 | const constexpr double_t DEFAULT_FIELD_DOUBLE = 0.345345; 33 | const constexpr double_t MODIFIED_FIELD_DOUBLE = 3.432; 34 | 35 | 36 | class TestConfig : public SerenityConfig { 37 | public: 38 | TestConfig() { 39 | this->initDefaults(); 40 | } 41 | 42 | /** 43 | * This constructor enables run-time overlapping of default 44 | * configuration records. 45 | */ 46 | explicit TestConfig(const SerenityConfig& customCfg) { 47 | this->initDefaults(); 48 | this->applyConfig(customCfg); 49 | } 50 | 51 | /** 52 | * Init default values for Test configuration. 53 | */ 54 | void initDefaults() { 55 | this->set(FIELD_STR, (std::string)DEFAULT_FIELD_STR); 56 | this->set(FIELD_BOOL, DEFAULT_FIELD_BOOL); 57 | this->set(FIELD_UINT, DEFAULT_FIELD_UINT); 58 | this->set(FIELD_INT, DEFAULT_FIELD_INT); 59 | this->set(FIELD_DOUBLE, DEFAULT_FIELD_DOUBLE); 60 | } 61 | }; 62 | 63 | 64 | TEST(SerenityConfigTest, DefaultValuesAvailable) { 65 | // Create empty config with no configuration fields. 66 | SerenityConfig newConfig; 67 | 68 | TestConfig internalConfig = TestConfig(newConfig); 69 | EXPECT_EQ(internalConfig.getS(FIELD_STR), (std::string)DEFAULT_FIELD_STR); 70 | EXPECT_EQ(internalConfig.getB(FIELD_BOOL), DEFAULT_FIELD_BOOL); 71 | EXPECT_EQ(internalConfig.getU64(FIELD_UINT), DEFAULT_FIELD_UINT); 72 | EXPECT_EQ(internalConfig.getI64(FIELD_INT), DEFAULT_FIELD_INT); 73 | EXPECT_EQ(internalConfig.getD(FIELD_DOUBLE), DEFAULT_FIELD_DOUBLE); 74 | } 75 | 76 | 77 | TEST(SerenityConfigTest, ModifiedValuesAvailable) { 78 | // Create config with custom configuration fields. 79 | SerenityConfig newConfig; 80 | newConfig.set(FIELD_STR, (std::string)MODIFIED_FIELD_STR); 81 | newConfig.set(FIELD_BOOL, MODIFIED_FIELD_BOOL); 82 | newConfig.set(FIELD_UINT, MODIFIED_FIELD_UINT); 83 | newConfig.set(FIELD_INT, MODIFIED_FIELD_INT); 84 | newConfig.set(FIELD_DOUBLE, MODIFIED_FIELD_DOUBLE); 85 | 86 | SerenityConfig internalConfig = TestConfig(newConfig); 87 | EXPECT_EQ(internalConfig.getS(FIELD_STR), (std::string)MODIFIED_FIELD_STR); 88 | EXPECT_EQ(internalConfig.getB(FIELD_BOOL), MODIFIED_FIELD_BOOL); 89 | EXPECT_EQ(internalConfig.getU64(FIELD_UINT), MODIFIED_FIELD_UINT); 90 | EXPECT_EQ(internalConfig.getI64(FIELD_INT), MODIFIED_FIELD_INT); 91 | EXPECT_EQ(internalConfig.getD(FIELD_DOUBLE), MODIFIED_FIELD_DOUBLE); 92 | } 93 | 94 | } // namespace tests 95 | } // namespace serenity 96 | } // namespace mesos 97 | 98 | -------------------------------------------------------------------------------- /src/tests/serenity/os_utils_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gtest/gtest.h" 5 | 6 | #include "stout/try.hpp" 7 | 8 | #include "serenity/os_utils.hpp" 9 | 10 | 11 | namespace mesos { 12 | namespace serenity { 13 | namespace tests { 14 | 15 | TEST(EnviromentVariableInitializer, GetEnvVariable) { 16 | const std::string envName = "SERENITY_TEST_ENV_VAR"; 17 | std::string envVal = "IT_WORKS"; 18 | 19 | // set test variable. 20 | int result = setenv(envName.c_str(), envVal.c_str(), 1); 21 | ASSERT_EQ(result, 0); 22 | 23 | Option res = GetEnviromentVariable(envName.c_str()); 24 | ASSERT_TRUE(res.isSome()); 25 | ASSERT_EQ(res.get(), envVal); 26 | } 27 | 28 | 29 | TEST(EnviromentVariableInitializer, GetUnexistantEnvVariable) { 30 | const std::string envName = "SERENITY_TEST_ENV_VAR_NON_EXISTENT"; 31 | 32 | Option res = GetEnviromentVariable(envName.c_str()); 33 | ASSERT_TRUE(res.isNone()); 34 | } 35 | 36 | 37 | TEST(GetHostname, GetHostname) { 38 | Try hostname = GetHostname(); 39 | ASSERT_TRUE(hostname.isSome()); 40 | } 41 | 42 | } // namespace tests 43 | } // namespace serenity 44 | } // namespace mesos 45 | 46 | -------------------------------------------------------------------------------- /src/tests/serenity/resource_helper_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include "stout/gtest.hpp" 6 | 7 | #include "mesos/mesos.hpp" 8 | 9 | #include "messages/serenity.hpp" 10 | 11 | #include "filters/executor_age.hpp" 12 | 13 | #include "observers/qos_correction.hpp" 14 | 15 | #include "serenity/resource_helper.hpp" 16 | 17 | #include "tests/common/usage_helper.hpp" 18 | #include "tests/common/mocks/mock_sink.hpp" 19 | #include "tests/common/sources/mock_source.hpp" 20 | 21 | namespace mesos { 22 | namespace serenity { 23 | namespace tests { 24 | 25 | // This fixture includes 5 executors: 26 | // - 1 BE <1 CPUS> id 0 27 | // - 2 BE <0.5 CPUS> id 1,2 28 | // - 1 PR <4 CPUS> id 3 29 | // - 1 PR <2 CPUS> id 4 30 | const char QOS_FIXTURE[] = "tests/fixtures/qos/average_usage.json"; 31 | 32 | /** 33 | * Check if getRevocableExecutors function properly filters out PR executors. 34 | * 35 | * TODO(skonefal): Does it really work? 36 | */ 37 | TEST(HelperFunctionsTest, getRevocableExecutors) { 38 | Try usages = JsonUsage::ReadJson(QOS_FIXTURE); 39 | if (usages.isError()) { 40 | LOG(ERROR) << "JsonSource failed: " << usages.error() << std::endl; 41 | } 42 | 43 | ResourceUsage usage; 44 | usage.CopyFrom(usages.get().resource_usage(0)); 45 | 46 | std::list ret = 47 | ResourceUsageHelper::getRevocableExecutors(usage); 48 | 49 | ASSERT_EQ(3u, ret.size()); 50 | 51 | // Expected only BE executors. 52 | for (auto executor : ret) { 53 | Resources allocated(executor.allocated()); 54 | EXPECT_FALSE(allocated.revocable().empty()); 55 | } 56 | } 57 | 58 | } // namespace tests 59 | } // namespace serenity 60 | } // namespace mesos 61 | -------------------------------------------------------------------------------- /src/tests/sources/json_source_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tests/common/sinks/dummy_sink.hpp" 5 | #include "tests/common/sources/json_source.hpp" 6 | 7 | namespace mesos { 8 | namespace serenity { 9 | namespace tests { 10 | 11 | TEST(JsonSource, ProduceRuFromFile) { 12 | DummySink dummySink; 13 | JsonSource jsonSource; 14 | jsonSource.addConsumer(&dummySink); 15 | jsonSource.RunTests("tests/fixtures/baseline_smoke_test_resource_usage.json"); 16 | 17 | ASSERT_EQ(dummySink.numberOfMessagesConsumed, 4); 18 | } 19 | 20 | } // namespace tests 21 | } // namespace serenity 22 | } // namespace mesos 23 | 24 | -------------------------------------------------------------------------------- /src/tests/time_series_export/backend/influx_db9_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "tests/common/sources/json_source.hpp" 4 | #include "tests/common/mocks/mock_sink.hpp" 5 | 6 | #include "time_series_export/backend/influx_db9.hpp" 7 | #include "time_series_export/resource_usage_ts_export.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | namespace tests { 12 | 13 | TEST(InfluxDb9BackendTests, PutMetricWithoutTags) { 14 | InfluxDb9Backend backend("localhost", "8086", "serenity", "root", "root"); 15 | 16 | const double_t VALUE = 48.0; 17 | TimeSeriesRecord record(Series::CPU_USAGE_SYS, VALUE); 18 | 19 | backend.PutMetric(record); 20 | } 21 | 22 | TEST(InfluxDb9BackendTests, PutMetricWithTag) { 23 | InfluxDb9Backend backend("localhost", "8086", "serenity", "root", "root"); 24 | 25 | const double_t VALUE = 66.0; 26 | const std::string HOSTNAME("localhostname"); 27 | TimeSeriesRecord record(Series::CYCLES, VALUE); 28 | record.setTag(TsTag::HOSTNAME, HOSTNAME); 29 | 30 | backend.PutMetric(record); 31 | } 32 | 33 | TEST(InfluxDb9BackendTests, PutMetricWithMultipleTags) { 34 | InfluxDb9Backend backend("localhost", "8086", "serenity", "root", "root"); 35 | 36 | const double_t VALUE = 88.0; 37 | const std::string FRAMEWORK_ID("framework_id_tag"); 38 | const std::string EXECUTOR_ID("executor_id_tag"); 39 | 40 | TimeSeriesRecord record(Series::INSTRUCTIONS, VALUE); 41 | record.setTag(TsTag::FRAMEWORK_ID, FRAMEWORK_ID); 42 | record.setTag(TsTag::EXECUTOR_ID, EXECUTOR_ID); 43 | 44 | backend.PutMetric(record); 45 | } 46 | 47 | } // namespace tests 48 | } // namespace serenity 49 | } // namespace mesos 50 | -------------------------------------------------------------------------------- /src/tests/time_series_export/resource_usage_ts_export_test.cpp: -------------------------------------------------------------------------------- 1 | #include "tests/common/sources/json_source.hpp" 2 | #include "tests/common/mocks/mock_sink.hpp" 3 | 4 | #include "time_series_export/backend/influx_db9.hpp" 5 | #include "time_series_export/resource_usage_ts_export.hpp" 6 | 7 | namespace mesos { 8 | namespace serenity { 9 | namespace tests { 10 | 11 | TEST(ResourceUsageTimeSeriesExport, BasicTest) { 12 | InfluxDb9Backend backend("localhost", "8086", "serenity", "root", "root"); 13 | ResourceUsageTimeSeriesExporter ruExporter("tagged-test", &backend); 14 | JsonSource jsonSource; 15 | MockSink mockSink; 16 | 17 | jsonSource.addConsumer(&ruExporter); 18 | jsonSource.addConsumer(&mockSink); 19 | 20 | jsonSource.RunTests( 21 | "tests/fixtures/baseline_smoke_test_resource_usage.json"); 22 | } 23 | 24 | } // namespace tests 25 | } // namespace serenity 26 | } // namespace mesos 27 | -------------------------------------------------------------------------------- /src/tests/time_series_export/slack_ts_export_test.cpp: -------------------------------------------------------------------------------- 1 | #include "observers/slack_resource.hpp" 2 | 3 | #include "tests/common/sources/json_source.hpp" 4 | #include "tests/common/mocks/mock_sink.hpp" 5 | 6 | #include "time_series_export/backend/influx_db9.hpp" 7 | #include "time_series_export/slack_ts_export.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | namespace tests { 12 | 13 | TEST(SlackResourceTimeSeriesExportTest, BasicTest) { 14 | SlackResourceObserver observer; 15 | InfluxDb9Backend backend("localhost", "8086", "serenity", "root", "root"); 16 | SlackTimeSeriesExporter slackExporter("tagged-test", &backend); 17 | JsonSource jsonSource; 18 | MockSink mockSink; 19 | 20 | jsonSource.addConsumer(&observer); 21 | observer.addConsumer(&mockSink); 22 | observer.addConsumer(&slackExporter); 23 | 24 | jsonSource.RunTests("tests/fixtures/slack_calculation_test.json"); 25 | } 26 | 27 | } // namespace tests 28 | } // namespace serenity 29 | } // namespace mesos 30 | -------------------------------------------------------------------------------- /src/time_series_export/backend/influx_db9.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "3rdparty/lib/curlcpp/include/curl_easy.h" 5 | 6 | #include "glog/logging.h" 7 | 8 | #include "serenity/metrics_helper.hpp" 9 | 10 | #include "time_series_export/backend/influx_db9.hpp" 11 | 12 | namespace mesos { 13 | namespace serenity { 14 | 15 | using curl::curl_easy; 16 | 17 | void InfluxDb9Backend::PutMetric(const TimeSeriesRecord& _tsRecord) { 18 | curl_easy easy = prepareRequest(_tsRecord); 19 | 20 | try { 21 | easy.perform(); 22 | } 23 | catch (curl_easy_exception error) { 24 | LOG(ERROR) << "InfluxDB9Backend: Error while inserting metrics, " 25 | << error.what(); 26 | } 27 | } 28 | 29 | const curl_easy InfluxDb9Backend::prepareRequest( 30 | const TimeSeriesRecord& _tsRecord) const { 31 | curl_easy easy; 32 | 33 | std::string url = getDbUrl(); 34 | std::string userpwd = getUserAndPassword(); 35 | std::string content = serializeRecord(_tsRecord); 36 | 37 | easy.add(curl_pair(CURLOPT_URL, url)); 38 | 39 | easy.add(curl_pair( 40 | CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); 41 | easy.add(curl_pair(CURLOPT_USERPWD, userpwd)); 42 | 43 | easy.add(curl_pair(CURLOPT_POST, 1)); 44 | easy.add(curl_pair(CURLOPT_POSTFIELDSIZE, 45 | content.size())); 46 | easy.add(curl_pair(CURLOPT_POSTFIELDS, content)); 47 | 48 | return easy; 49 | } 50 | 51 | /** 52 | * Returns URL to database. 53 | * i.e. http://localhost:8086/write?db=mydb 54 | */ 55 | const std::string InfluxDb9Backend::getDbUrl() const { 56 | constexpr uint32_t kBufferLen = 256; 57 | char buffer[kBufferLen]; 58 | snprintf(buffer, kBufferLen, "http://%s:%d/write?db=%s", 59 | this->influxDbAddress.c_str(), 60 | this->influxDbPort, 61 | this->influxDbDatabaseName.c_str()); 62 | 63 | return std::string(buffer); 64 | } 65 | 66 | const std::string InfluxDb9Backend::getUserAndPassword() const { 67 | std::stringstream stringstream; 68 | stringstream << influxDbUser << ":" << influxDbPass; 69 | return stringstream.str(); 70 | } 71 | 72 | /** 73 | * Serializes record to InfluxDB9 format 74 | * measurement,tag=val,tkey2=tval2 fkey=fval,fkey2=fval2 1234567890000000000 75 | * , 76 | */ 77 | const std::string InfluxDb9Backend::serializeRecord( 78 | const TimeSeriesRecord& _tsRecord) const { 79 | constexpr char SPACE_SEP = ' '; 80 | constexpr char COMMA_SEP = ','; 81 | const constexpr char* VALUE = "value"; 82 | std::stringstream record; 83 | 84 | record << _tsRecord.getSeriesName(); 85 | 86 | for (const auto& tag : _tsRecord.getTags()) { 87 | record << COMMA_SEP << tag.first << "=" << tag.second; 88 | } 89 | 90 | record << SPACE_SEP << VALUE << "=" << _tsRecord.getValue(); 91 | 92 | return record.str(); 93 | } 94 | 95 | const std::string InfluxDb9Backend::initializeField( 96 | Option _parameterValue, 97 | Option _envVariableName, 98 | std::string _defaultValue) { 99 | if (_parameterValue.isSome()) { 100 | return _parameterValue.get(); 101 | } 102 | if (_envVariableName.isSome()) { 103 | Option result = GetEnviromentVariable( 104 | _envVariableName.get()); 105 | if (result.isSome()) { 106 | return result.get(); 107 | } 108 | } 109 | return _defaultValue; 110 | } 111 | 112 | } // namespace serenity 113 | } // namespace mesos 114 | -------------------------------------------------------------------------------- /src/time_series_export/backend/influx_db9.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_INFLUX_DB9_HPP 2 | #define SERENITY_INFLUX_DB9_HPP 3 | 4 | #include // NOLINT [build/c++11] 5 | #include 6 | 7 | #include "curl_easy.h" // NOLINT(build/include) 8 | 9 | #include "serenity/os_utils.hpp" 10 | 11 | #include "time_series_backend.hpp" 12 | 13 | namespace mesos { 14 | namespace serenity { 15 | 16 | class InfluxDb9Backend : public TimeSeriesBackend { 17 | public: 18 | InfluxDb9Backend(Option _influxDbAddres = None(), 19 | Option _influxDbPort = None(), 20 | Option _influxDbDatabaseName = None(), 21 | Option _influxDbUser = None(), 22 | Option _influxDbPass = None()) : 23 | influxDbAddress(initializeField( 24 | _influxDbAddres, 25 | "INFLUXDB_ADDRESS", 26 | "influxdb-monitoring.marathon.mesos")), 27 | influxDbPort(std::stoi(initializeField( 28 | _influxDbPort, 29 | "INFLUXDB_PORT", 30 | "8086"))), 31 | influxDbDatabaseName(initializeField( 32 | _influxDbDatabaseName, 33 | "INFLUXDB_DB_NAME", 34 | "serenity")), 35 | influxDbUser(initializeField( 36 | _influxDbUser, 37 | "INFLUXDB_USER", 38 | "root")), 39 | influxDbPass(initializeField( 40 | _influxDbPass, 41 | "INFLUXDB_PASSWORD", 42 | "root")) {} 43 | 44 | virtual void PutMetric(const TimeSeriesRecord& _timeSeriesRecord); 45 | 46 | protected: 47 | const curl::curl_easy prepareRequest(const TimeSeriesRecord& _tsRecord) const; 48 | const std::string getDbUrl() const; 49 | const std::string getUserAndPassword() const; 50 | const std::string serializeRecord(const TimeSeriesRecord& _tsRecord) const; 51 | 52 | /** 53 | * Initialization helper for constructor. 54 | * Returns values in order: 55 | * - if _constructorValue.isSome - return _constructorValue.get 56 | * - if _serviceName.isSome - 57 | * - if _enviromenetVariable is true - return enviroment variable 58 | * - else return default value 59 | */ 60 | const std::string initializeField(Option _parameterValue, 61 | Option _envVariableName, 62 | std::string _defaultValue); 63 | 64 | const std::string influxDbDatabaseName; 65 | const std::string influxDbAddress; 66 | const uint32_t influxDbPort; 67 | 68 | const std::string influxDbUser; 69 | const std::string influxDbPass; 70 | 71 | static constexpr auto timePrecision = std::nano(); 72 | }; 73 | 74 | } // namespace serenity 75 | } // namespace mesos 76 | 77 | #endif // SERENITY_INFLUX_DB9_HPP 78 | -------------------------------------------------------------------------------- /src/time_series_export/backend/time_series_backend.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_TIME_SERIES_BACKEND_HPP 2 | #define SERENITY_TIME_SERIES_BACKEND_HPP 3 | 4 | #include 5 | 6 | #include "time_series_export/backend/time_series_record.hpp" 7 | 8 | namespace mesos { 9 | namespace serenity { 10 | 11 | /** 12 | * Time Series backend interface. 13 | */ 14 | class TimeSeriesBackend { 15 | public: 16 | virtual void PutMetric(const TimeSeriesRecord& _timeSeriesRecord) = 0; 17 | 18 | virtual void PutMetric(const std::vector& _recordList){ 19 | for (const auto& record : _recordList) { 20 | this->PutMetric(record); 21 | } 22 | } 23 | }; 24 | 25 | } // namespace serenity 26 | } // namespace mesos 27 | 28 | #endif // SERENITY_TIME_SERIES_BACKEND_HPP 29 | -------------------------------------------------------------------------------- /src/time_series_export/resource_usage_ts_export.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_RESOURCE_USAGE_TIME_SERIES_EXPORTER_HPP 2 | #define SERENITY_RESOURCE_USAGE_TIME_SERIES_EXPORTER_HPP 3 | 4 | #include 5 | 6 | #include "backend/time_series_backend.hpp" 7 | #include "backend/influx_db9.hpp" 8 | 9 | #include "mesos/mesos.hpp" 10 | #include "mesos/resources.hpp" 11 | 12 | #include "serenity/serenity.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | /** 18 | * Time series exporter for ResourceUsage message. 19 | * 20 | * @param _timeSeriesBackend: Time Series Backend. 21 | * @param _tag: Custom tag added to every sample. 22 | */ 23 | class ResourceUsageTimeSeriesExporter : public Consumer { 24 | public: 25 | ResourceUsageTimeSeriesExporter( 26 | std::string _tag = "", 27 | TimeSeriesBackend* _timeSeriesBackend = new InfluxDb9Backend()) : 28 | timeSeriesBackend(_timeSeriesBackend), 29 | customTag(_tag) {} 30 | 31 | Try consume(const ResourceUsage& resources) override; 32 | 33 | protected: 34 | TimeSeriesBackend* timeSeriesBackend; 35 | 36 | std::string hostname; 37 | 38 | const std::string customTag; //!< Custom tag that is added to every sample. 39 | }; 40 | 41 | } // namespace serenity 42 | } // namespace mesos 43 | 44 | #endif // SERENITY_RESOURCE_USAGE_TIME_SERIES_EXPORTER_HPP 45 | -------------------------------------------------------------------------------- /src/time_series_export/slack_ts_export.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "glog/logging.h" 4 | 5 | #include "serenity/agent_utils.hpp" 6 | 7 | #include "slack_ts_export.hpp" 8 | 9 | namespace mesos { 10 | namespace serenity { 11 | 12 | Try SlackTimeSeriesExporter::consume(const Resources& resources) { 13 | double_t cpus = 0.0; 14 | 15 | Option cpus_option = resources.cpus(); 16 | if (cpus_option.isSome()) { 17 | cpus = cpus_option.get(); 18 | } 19 | 20 | Try hostname = AgentInfo::GetHostName(); 21 | if (hostname.isError()) { 22 | LOG(ERROR) << "ResourceUsageTimeSeriesExporter: cannot get hostname"; 23 | return Nothing(); // Do not cause failure in pipeline due to 24 | // stats reporting failure 25 | } 26 | Try agentId = AgentInfo::GetAgentId(); 27 | if (agentId.isError()) { 28 | LOG(ERROR) << "ResourceUsageTimeSeriesExporter: cannot get agent id"; 29 | return Nothing(); // Do not cause failure in pipeline due to 30 | // stats reporting failure 31 | } 32 | 33 | TimeSeriesRecord record(Series::SLACK_RESOURCES, cpus); 34 | record.setTag(TsTag::HOSTNAME, hostname.get()); 35 | record.setTag(TsTag::AGENT_ID, agentId.get()); 36 | record.setTag(TsTag::TAG, this->customTag); 37 | 38 | timeSeriesBackend->PutMetric(record); 39 | 40 | return Nothing(); 41 | } 42 | 43 | } // namespace serenity 44 | } // namespace mesos 45 | -------------------------------------------------------------------------------- /src/time_series_export/slack_ts_export.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERENITY_SLACK_TIME_SERIES_EXPORTER_HPP 2 | #define SERENITY_SLACK_TIME_SERIES_EXPORTER_HPP 3 | 4 | #include 5 | 6 | #include "backend/time_series_backend.hpp" 7 | #include "backend/influx_db9.hpp" 8 | 9 | #include "mesos/mesos.hpp" 10 | #include "mesos/resources.hpp" 11 | 12 | #include "serenity/serenity.hpp" 13 | 14 | namespace mesos { 15 | namespace serenity { 16 | 17 | class SlackTimeSeriesExporter : public Consumer { 18 | public: 19 | SlackTimeSeriesExporter( 20 | std::string _tag = "", 21 | TimeSeriesBackend* _timeSeriesBackend = new InfluxDb9Backend()) : 22 | timeSeriesBackend(_timeSeriesBackend), 23 | customTag(_tag) {} 24 | 25 | Try consume(const Resources& resources) override; 26 | 27 | protected: 28 | TimeSeriesBackend* timeSeriesBackend; 29 | 30 | const std::string customTag; //!< Custom tag that is added to every sample. 31 | }; 32 | 33 | } // namespace serenity 34 | } // namespace mesos 35 | 36 | #endif // SERENITY_SLACK_TIME_SERIES_EXPORTER_HPP 37 | --------------------------------------------------------------------------------