diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e5bf7ab357c8db4737f6e6e4d2de5098cd1a8583..837edab2e05ca3a597bffa267f88fbe1016a7b8f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -33,10 +33,16 @@ default:
   script:
     - mkdir -p /kaniko/.docker
     - echo "{\"auths\":{\"${CR_HOST}\":{\"auth\":\"$(printf "%s:%s" "${CR_USER}" "${CR_PW}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
-    - DOCKER_TAG_NAME=$(echo $CI_COMMIT_REF_SLUG- | sed 's/^master-$//')
-    - "[ ! $CI_COMMIT_TAG ] && KANIKO_D=\"$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:${DOCKER_TAG_NAME}latest\""
-    - "[ ! $CI_COMMIT_TAG ] && KANIKO_D=\"$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:$DOCKER_TAG_NAME$CI_COMMIT_SHORT_SHA\""
-    - "[ $CI_COMMIT_TAG ] && KANIKO_D=\"$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:$CI_COMMIT_TAG\""
+    - >
+      if [ $IMAGE_TAG ]; then
+        KANIKO_D="$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:$IMAGE_TAG"
+      elif [ $CI_COMMIT_TAG ]; then
+        KANIKO_D="$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:$CI_COMMIT_TAG"
+      else
+        DOCKER_TAG_NAME=$(echo $CI_COMMIT_REF_SLUG- | sed 's/^master-$//')
+        KANIKO_D="$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:${DOCKER_TAG_NAME}latest"
+        KANIKO_D="$KANIKO_D -d $CR_HOST/$CR_ORG/$IMAGE_NAME:$DOCKER_TAG_NAME$CI_COMMIT_SHORT_SHA"
+      fi
     - "[ $DOCKERFILE ] && KANIKO_DOCKERFILE=\"--dockerfile $DOCKERFILE\""
     - /kaniko/executor --context `pwd`/$CONTEXT $KANIKO_DOCKERFILE $KANIKO_D
 
@@ -71,7 +77,7 @@ test-docs-links:
   extends: .docs
   needs:
     - build-docs
-  script: bundle exec htmlproofer --assume-extension --allow_hash_href --url-ignore "/favicon.ico" ./_site
+  script: bundle exec htmlproofer --assume-extension --allow_hash_href ./_site
 
 build-docs-crds:
   stage: build
@@ -115,6 +121,11 @@ lint-helm:
     name: alpine/helm:3.5.2
     entrypoint: [""]
   script: helm lint helm/
+  rules:
+  - changes:
+    - helm/*
+  - when: manual
+    allow_failure: true
 
 
 # Theodolite Benchmarks
@@ -154,6 +165,7 @@ test-benchmarks:
     - build-benchmarks
   script: ./gradlew test --continue
   artifacts:
+    when: always
     reports:
       junit:
         - "theodolite-benchmarks/**/build/test-results/test/TEST-*.xml"
@@ -215,10 +227,8 @@ spotbugs-benchmarks:
     - changes:
       - theodolite-benchmarks/*
       - theodolite-benchmarks/$JAVA_PROJECT_NAME/**/*
-      - theodolite-benchmarks/kstreams-commons/**/*
-      - theodolite-benchmarks/flink-commons/**/*
-      - theodolite-benchmarks/load-generator-commons/**/*
-      if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW && $IMAGE_NAME && $JAVA_PROJECT_NAME"
+      - theodolite-benchmarks/{$JAVA_PROJECT_DEPS}/**/*
+      if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW && $IMAGE_NAME && $JAVA_PROJECT_NAME && $JAVA_PROJECT_DEPS"
     - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW && $IMAGE_NAME && $JAVA_PROJECT_NAME"
       when: manual
       allow_failure: true
@@ -228,72 +238,140 @@ deploy-uc1-kstreams:
   variables:
     IMAGE_NAME: "theodolite-uc1-kstreams-app"
     JAVA_PROJECT_NAME: "uc1-kstreams"
+    JAVA_PROJECT_DEPS: "kstreams-commons"
 
 deploy-uc2-kstreams:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc2-kstreams-app"
     JAVA_PROJECT_NAME: "uc2-kstreams"
+    JAVA_PROJECT_DEPS: "kstreams-commons"
 
 deploy-uc3-kstreams:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc3-kstreams-app"
     JAVA_PROJECT_NAME: "uc3-kstreams"
+    JAVA_PROJECT_DEPS: "kstreams-commons"
 
 deploy-uc4-kstreams:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc4-kstreams-app"
     JAVA_PROJECT_NAME: "uc4-kstreams"
+    JAVA_PROJECT_DEPS: "kstreams-commons"
 
 deploy-uc1-flink:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc1-flink"
     JAVA_PROJECT_NAME: "uc1-flink"
+    JAVA_PROJECT_DEPS: "flink-commons"
 
 deploy-uc2-flink:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc2-flink"
     JAVA_PROJECT_NAME: "uc2-flink"
+    JAVA_PROJECT_DEPS: "flink-commons"
 
 deploy-uc3-flink:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc3-flink"
     JAVA_PROJECT_NAME: "uc3-flink"
+    JAVA_PROJECT_DEPS: "flink-commons"
 
 deploy-uc4-flink:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc4-flink"
     JAVA_PROJECT_NAME: "uc4-flink"
+    JAVA_PROJECT_DEPS: "flink-commons"
+
+deploy-uc1-beam-flink:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc1-beam-flink"
+    JAVA_PROJECT_NAME: "uc1-beam-flink"
+    JAVA_PROJECT_DEPS: "beam-commons,uc1-beam"
+
+deploy-uc2-beam-flink:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc2-beam-flink"
+    JAVA_PROJECT_NAME: "uc2-beam-flink"
+    JAVA_PROJECT_DEPS: "beam-commons,uc2-beam"
+
+deploy-uc3-beam-flink:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc3-beam-flink"
+    JAVA_PROJECT_NAME: "uc3-beam-flink"
+    JAVA_PROJECT_DEPS: "beam-commons,uc3-beam"
+
+deploy-uc4-beam-flink:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc4-beam-flink"
+    JAVA_PROJECT_NAME: "uc4-beam-flink"
+    JAVA_PROJECT_DEPS: "beam-commons,uc4-beam"
+
+deploy-uc1-beam-samza:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc1-beam-samza"
+    JAVA_PROJECT_NAME: "uc1-beam-samza"
+    JAVA_PROJECT_DEPS: "beam-commons,uc1-beam"
+
+deploy-uc2-beam-samza:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc2-beam-samza"
+    JAVA_PROJECT_NAME: "uc2-beam-samza"
+    JAVA_PROJECT_DEPS: "beam-commons,uc2-beam"
+
+deploy-uc3-beam-samza:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc3-beam-samza"
+    JAVA_PROJECT_NAME: "uc3-beam-samza"
+    JAVA_PROJECT_DEPS: "beam-commons,uc3-beam"
+
+deploy-uc4-beam-samza:
+  extends: .deploy-benchmarks
+  variables:
+    IMAGE_NAME: "theodolite-uc4-beam-samza"
+    JAVA_PROJECT_NAME: "uc4-beam-samza"
+    JAVA_PROJECT_DEPS: "beam-commons,uc4-beam"
 
 deploy-uc1-load-generator:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc1-workload-generator"
     JAVA_PROJECT_NAME: "uc1-load-generator"
+    JAVA_PROJECT_DEPS: "load-generator-commons"
 
 deploy-uc2-load-generator:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc2-workload-generator"
     JAVA_PROJECT_NAME: "uc2-load-generator"
+    JAVA_PROJECT_DEPS: "load-generator-commons"
 
 deploy-uc3-load-generator:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc3-workload-generator"
     JAVA_PROJECT_NAME: "uc3-load-generator"
+    JAVA_PROJECT_DEPS: "load-generator-commons"
 
 deploy-uc4-load-generator:
   extends: .deploy-benchmarks
   variables:
     IMAGE_NAME: "theodolite-uc4-workload-generator"
     JAVA_PROJECT_NAME: "uc4-load-generator"
+    JAVA_PROJECT_DEPS: "load-generator-commons"
       
 
 # Theodolite Framework
@@ -311,6 +389,11 @@ deploy-uc4-load-generator:
   before_script:
     - export GRADLE_USER_HOME=`pwd`/.gradle
     - cd theodolite
+  rules:
+    - changes:
+      - theodolite/**/*
+    - when: manual
+      allow_failure: true
 
 build-theodolite-jvm:
   stage: build
@@ -318,8 +401,10 @@ build-theodolite-jvm:
   script: ./gradlew --build-cache assemble
   artifacts:
     paths:
-      - "theodolite/build/lib/*"
-      - "theodolite/build/*-runner.jar"
+      - "theodolite/build/quarkus-app/lib/"
+      - "theodolite/build/quarkus-app/*.jar"
+      - "theodolite/build/quarkus-app/app/"
+      - "theodolite/build/quarkus-app/quarkus/"
     expire_in: 6 hours
 
 build-theodolite-native:
@@ -341,6 +426,7 @@ test-theodolite:
     #- build-theodolite-native
   script: ./gradlew test --stacktrace
   artifacts:
+    when: always
     reports:
       junit:
         - "theodolite/**/build/test-results/test/TEST-*.xml"
@@ -419,6 +505,22 @@ test-slo-checker-dropped-records-kstreams:
     - when: manual
       allow_failure: true
 
+test-slo-checker-generic:
+  stage: test
+  needs: []
+  image: python:3.7-slim
+  before_script:
+    - cd slo-checker/generic
+  script:
+    - pip install -r requirements.txt
+    - cd app
+    - python -m unittest
+  rules:
+    - changes:
+      - slo-checker/generic/**/*
+    - when: manual
+      allow_failure: true
+
 deploy-slo-checker-lag-trend:
   stage: deploy
   extends:
@@ -455,6 +557,24 @@ deploy-slo-checker-dropped-records-kstreams:
       when: manual
       allow_failure: true
 
+deploy-slo-checker-generic:
+  stage: deploy
+  extends:
+    - .kaniko-push
+  needs:
+    - test-slo-checker-generic
+  before_script:
+    - cd slo-checker/generic
+  variables:
+    IMAGE_NAME: theodolite-slo-checker-generic
+  rules:
+    - changes:
+      - slo-checker/generic/**/*
+      if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
+    - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
+      when: manual
+      allow_failure: true
+
 
 # Theodolite Random Scheduler
 
@@ -474,4 +594,22 @@ deploy-random-scheduler:
     - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
       when: manual
       allow_failure: true
-      
\ No newline at end of file
+
+deploy-buildimage-docker-compose-jq:
+  stage: deploy
+  extends:
+    - .kaniko-push
+  needs: []
+  variables:
+    DOCKER_VERSION: 20.10.12
+    IMAGE_NAME: theodolite-build-docker-compose-jq
+    IMAGE_TAG: $DOCKER_VERSION
+  before_script:
+    - cd buildimages/docker-compose-jq
+  rules:
+    - changes:
+      - buildimages/docker-compose-jq/Dockerfile
+      if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
+    - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW && $CI_PIPELINE_SOURCE == 'web'"
+      when: manual
+      allow_failure: true
diff --git a/CITATION.cff b/CITATION.cff
index ca94e1c5039d3aeac3a4535767d5217de4960a6f..04640de442f4458b09e11ce3d2939c850f594556 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -8,7 +8,7 @@ authors:
     given-names: Wilhelm
     orcid: "https://orcid.org/0000-0001-6625-4335"
 title: Theodolite
-version: "0.5.1"
+version: "0.6.3"
 repository-code: "https://github.com/cau-se/theodolite"
 license: "Apache-2.0"
 doi: "10.1016/j.bdr.2021.100209"
diff --git a/binder/requirements.txt b/binder/requirements.txt
new file mode 120000
index 0000000000000000000000000000000000000000..6de15663a8c83876719aa07d6cb09b5a7b71df21
--- /dev/null
+++ b/binder/requirements.txt
@@ -0,0 +1 @@
+../analysis/requirements.txt
\ No newline at end of file
diff --git a/buildimages/docker-compose-jq/Dockerfile b/buildimages/docker-compose-jq/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..cd3f60ba3e75ab5767acff788c3bb69c8640cd4c
--- /dev/null
+++ b/buildimages/docker-compose-jq/Dockerfile
@@ -0,0 +1,6 @@
+FROM docker:${DOCKER_VERSION:-latest}
+
+RUN apk update && \
+    apk add jq && \
+    apk add py-pip python3-dev libffi-dev openssl-dev gcc libc-dev rust cargo make && \
+    pip install docker-compose
diff --git a/codemeta.json b/codemeta.json
index a158e30eb7f1ab433779678aba3a1cc3b7e33c80..832b570681afb143978698fd47dad5d2835c700b 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -5,16 +5,20 @@
     "codeRepository": "https://github.com/cau-se/theodolite",
     "dateCreated": "2020-03-13",
     "datePublished": "2020-07-27",
-    "dateModified": "2021-11-12",
+    "dateModified": "2022-01-24",
     "downloadUrl": "https://github.com/cau-se/theodolite/releases",
     "name": "Theodolite",
-    "version": "0.5.1",
-    "description": "Theodolite is a framework for benchmarking the horizontal and vertical scalability of stream processing engines.",
+    "version": "0.6.3",
+    "description": "Theodolite is a framework for benchmarking the horizontal and vertical scalability of cloud-native applications.",
     "developmentStatus": "active",
+    "relatedLink": [
+        "https://www.theodolite.rocks"
+    ],
     "referencePublication": "https://doi.org/10.1016/j.bdr.2021.100209",
     "programmingLanguage": [
-        "Python",
-        "Java"
+        "Kotlin",
+        "Java",
+        "Python"
     ],
     "runtimePlatform": [
         "Kubernetes"
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 1dbb7d6b6a9eec5d8d5484b15acd5ad870bc2ed9..34ed67d70b1212b14bbf47a2062cf1138817df63 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -7,7 +7,7 @@ GEM
       minitest (~> 5.1)
       tzinfo (~> 1.1)
       zeitwerk (~> 2.2, >= 2.2.2)
-    addressable (2.7.0)
+    addressable (2.8.0)
       public_suffix (>= 2.0.2, < 5.0)
     coffee-script (2.4.1)
       coffee-script-source
@@ -227,7 +227,7 @@ GEM
       jekyll-seo-tag (~> 2.1)
     minitest (5.14.4)
     multipart-post (2.1.1)
-    nokogiri (1.11.7-x86_64-linux)
+    nokogiri (1.13.0-x86_64-linux)
       racc (~> 1.4)
     nokogumbo (2.0.5)
       nokogiri (~> 1.8, >= 1.8.4)
@@ -238,7 +238,7 @@ GEM
     pathutil (0.16.2)
       forwardable-extended (~> 2.6)
     public_suffix (4.0.6)
-    racc (1.5.2)
+    racc (1.6.0)
     rainbow (3.0.0)
     rb-fsevent (0.11.0)
     rb-inotify (0.10.1)
diff --git a/docs/_config.yml b/docs/_config.yml
index 723f76109c090e95be3095076dcc0f7af3847eca..0d2a1aa774a83347c80b538a97d5dbfa1b7639b3 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,10 +1,9 @@
 title: "Theodolite"
 description: >-
   Theodolite is a framework for benchmarking the horizontal and vertical
-  scalability of stream processing engines.
+  scalability of cloud-native applications.
 
 remote_theme: pmarsceill/just-the-docs
-#color_scheme: "dark"
 aux_links:
     "Theodolite on GitHub":
       - "//github.com/cau-se/theodolite"
@@ -14,4 +13,5 @@ exclude:
   - Gemfile
   - Gemfile.lock
   - README.md
-  - vendor
+  - vendor/
+  - drafts/
diff --git a/docs/api-reference/crds.md b/docs/api-reference/crds.md
index a8ce1d8d0ad2827fe07e0eee0a60a0cd2c71c93c..fb3f02ac941870dd085d06027d972e6003c7aadb 100644
--- a/docs/api-reference/crds.md
+++ b/docs/api-reference/crds.md
@@ -94,13 +94,6 @@ Resource Types:
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspeckafkaconfig">kafkaConfig</a></b></td>
-        <td>object</td>
-        <td>
-          Contains the Kafka configuration.<br/>
-        </td>
-        <td>true</td>
-      </tr><tr>
         <td><b><a href="#benchmarkspecloadgenerator">loadGenerator</a></b></td>
         <td>object</td>
         <td>
@@ -137,6 +130,13 @@ Resource Types:
             <i>Default</i>: map[]<br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspeckafkaconfig">kafkaConfig</a></b></td>
+        <td>object</td>
+        <td>
+          Contains the Kafka configuration.<br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b>name</b></td>
         <td>string</td>
@@ -150,12 +150,12 @@ Resource Types:
 </table>
 
 
-### benchmark.spec.kafkaConfig
+### benchmark.spec.loadGenerator
 <sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
 
 
 
-Contains the Kafka configuration.
+The loadGenResourceSets specifies all Kubernetes resources required to start the load generator. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
 
 <table>
     <thead>
@@ -167,25 +167,38 @@ Contains the Kafka configuration.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>bootstrapServer</b></td>
-        <td>string</td>
+        <td><b><a href="#benchmarkspecloadgeneratorafteractionsindex">afterActions</a></b></td>
+        <td>[]object</td>
         <td>
-          The bootstrap servers connection string.<br/>
+          Load generator after actions are executed after the teardown of the load generator.<br/>
+          <br/>
+            <i>Default</i>: []<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b><a href="#benchmarkspeckafkaconfigtopicsindex">topics</a></b></td>
+        <td><b><a href="#benchmarkspecloadgeneratorbeforeactionsindex">beforeActions</a></b></td>
         <td>[]object</td>
         <td>
-          List of topics to be created for each experiment. Alternative theodolite offers the possibility to remove certain topics after each experiment.<br/>
+          Load generator before actions are executed before the load generator is started.<br/>
+          <br/>
+            <i>Default</i>: []<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorresourcesindex">resources</a></b></td>
+        <td>[]object</td>
+        <td>
+          <br/>
+          <br/>
+            <i>Default</i>: []<br/>
+        </td>
+        <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.kafkaConfig.topics[index]
-<sup><sup>[↩ Parent](#benchmarkspeckafkaconfig)</sup></sup>
+### benchmark.spec.loadGenerator.afterActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecloadgenerator)</sup></sup>
 
 
 
@@ -201,51 +214,196 @@ Contains the Kafka configuration.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>name</b></td>
+        <td><b><a href="#benchmarkspecloadgeneratorafteractionsindexexec">exec</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies command to be executed.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorafteractionsindexselector">selector</a></b></td>
+        <td>object</td>
+        <td>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.afterActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorafteractionsindex)</sup></sup>
+
+
+
+Specifies command to be executed.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>command</b></td>
+        <td>[]string</td>
+        <td>
+          The command to be executed as string array.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>timeoutSeconds</b></td>
+        <td>integer</td>
+        <td>
+          Specifies the timeout (in seconds) for the specified command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.afterActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorafteractionsindex)</sup></sup>
+
+
+
+The selector specifies which resource should be selected for the execution of the command.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>container</b></td>
         <td>string</td>
         <td>
-          The name of the topic.<br/>
+          Specifies the container.<br/>
           <br/>
             <i>Default</i>: <br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b>numPartitions</b></td>
-        <td>integer</td>
+        <td><b><a href="#benchmarkspecloadgeneratorafteractionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
         <td>
-          The number of partitions of the topic.<br/>
+          Specifies the pod.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.afterActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorafteractionsindexselector)</sup></sup>
+
+
+
+Specifies the pod.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
+        <td>
+          The matchLabels of the desired pod.<br/>
           <br/>
-            <i>Default</i>: 0<br/>
+            <i>Default</i>: map[]<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.beforeActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecloadgenerator)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorbeforeactionsindexexec">exec</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies command to be executed.<br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b>removeOnly</b></td>
-        <td>boolean</td>
+        <td><b><a href="#benchmarkspecloadgeneratorbeforeactionsindexselector">selector</a></b></td>
+        <td>object</td>
         <td>
-          Determines if this topic should only be deleted after each experiement. For removeOnly topics the name can be a RegEx describing the topic.<br/>
-          <br/>
-            <i>Default</i>: false<br/>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.beforeActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorbeforeactionsindex)</sup></sup>
+
+
+
+Specifies command to be executed.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>command</b></td>
+        <td>[]string</td>
+        <td>
+          The command to be executed as string array.<br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b>replicationFactor</b></td>
+        <td><b>timeoutSeconds</b></td>
         <td>integer</td>
         <td>
-          The replication factor of the topic.<br/>
-          <br/>
-            <i>Default</i>: 0<br/>
+          Specifies the timeout (in seconds) for the specified command.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.loadGenerator
-<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+### benchmark.spec.loadGenerator.beforeActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorbeforeactionsindex)</sup></sup>
 
 
 
-The loadGenResourceSets specifies all Kubernetes resources required to start the load generator. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
+The selector specifies which resource should be selected for the execution of the command.
 
 <table>
     <thead>
@@ -257,20 +415,633 @@ The loadGenResourceSets specifies all Kubernetes resources required to start the
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecloadgeneratorresourcesindex">resources</a></b></td>
-        <td>[]object</td>
+        <td><b>container</b></td>
+        <td>string</td>
         <td>
+          Specifies the container.<br/>
           <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorbeforeactionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies the pod.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.beforeActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorbeforeactionsindexselector)</sup></sup>
+
+
+
+Specifies the pod.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
+        <td>
+          The matchLabels of the desired pod.<br/>
           <br/>
-            <i>Default</i>: []<br/>
+            <i>Default</i>: map[]<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.resources[index]
+<sup><sup>[↩ Parent](#benchmarkspecloadgenerator)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorresourcesindexconfigmap">configMap</a></b></td>
+        <td>object</td>
+        <td>
+          The configMap resourceSet loads the Kubernetes manifests from an Kubernetes configMap.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecloadgeneratorresourcesindexfilesystem">fileSystem</a></b></td>
+        <td>object</td>
+        <td>
+          The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.resources[index].configMap
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorresourcesindex)</sup></sup>
+
+
+
+The configMap resourceSet loads the Kubernetes manifests from an Kubernetes configMap.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>files</b></td>
+        <td>[]string</td>
+        <td>
+          (Optional) Specifies which files from the configMap should be loaded. If this field is not set, all files are loaded.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>name</b></td>
+        <td>string</td>
+        <td>
+          The name of the configMap<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadGenerator.resources[index].fileSystem
+<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorresourcesindex)</sup></sup>
+
+
+
+The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>files</b></td>
+        <td>[]string</td>
+        <td>
+          (Optional) Specifies which files from the configMap should be loaded. If this field is not set, all files are loaded.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>path</b></td>
+        <td>string</td>
+        <td>
+          The path to the folder which contains the Kubernetes manifests files.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadTypes[index]
+<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecloadtypesindexpatchersindex">patchers</a></b></td>
+        <td>[]object</td>
+        <td>
+          List of patchers used to scale this resource type.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>typeName</b></td>
+        <td>string</td>
+        <td>
+          Name of the load type.<br/>
+        </td>
+        <td>true</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.loadTypes[index].patchers[index]
+<sup><sup>[↩ Parent](#benchmarkspecloadtypesindex)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>resource</b></td>
+        <td>string</td>
+        <td>
+          Specifies the Kubernetes resource to be patched.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>type</b></td>
+        <td>string</td>
+        <td>
+          Type of the Patcher.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>properties</b></td>
+        <td>map[string]string</td>
+        <td>
+          (Optional) Patcher specific additional arguments.<br/>
+          <br/>
+            <i>Default</i>: map[]<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.resourceTypes[index]
+<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecresourcetypesindexpatchersindex">patchers</a></b></td>
+        <td>[]object</td>
+        <td>
+          List of patchers used to scale this resource type.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>typeName</b></td>
+        <td>string</td>
+        <td>
+          Name of the resource type.<br/>
+        </td>
+        <td>true</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.resourceTypes[index].patchers[index]
+<sup><sup>[↩ Parent](#benchmarkspecresourcetypesindex)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>resource</b></td>
+        <td>string</td>
+        <td>
+          Specifies the Kubernetes resource to be patched.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>type</b></td>
+        <td>string</td>
+        <td>
+          Type of the patcher.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>properties</b></td>
+        <td>map[string]string</td>
+        <td>
+          (Optional) Patcher specific additional arguments.<br/>
+          <br/>
+            <i>Default</i>: map[]<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut
+<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+
+
+
+The appResourceSets specifies all Kubernetes resources required to start the sut. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecsutafteractionsindex">afterActions</a></b></td>
+        <td>[]object</td>
+        <td>
+          <br/>
+          <br/>
+            <i>Default</i>: []<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutbeforeactionsindex">beforeActions</a></b></td>
+        <td>[]object</td>
+        <td>
+          SUT before actions are executed before the SUT is started.<br/>
+          <br/>
+            <i>Default</i>: []<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutresourcesindex">resources</a></b></td>
+        <td>[]object</td>
+        <td>
+          <br/>
+          <br/>
+            <i>Default</i>: []<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.afterActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecsut)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecsutafteractionsindexexec">exec</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies command to be executed.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutafteractionsindexselector">selector</a></b></td>
+        <td>object</td>
+        <td>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.afterActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecsutafteractionsindex)</sup></sup>
+
+
+
+Specifies command to be executed.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>command</b></td>
+        <td>[]string</td>
+        <td>
+          The command to be executed as string array.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>timeoutSeconds</b></td>
+        <td>integer</td>
+        <td>
+          Specifies the timeout (in seconds) for the specified command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.afterActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecsutafteractionsindex)</sup></sup>
+
+
+
+The selector specifies which resource should be selected for the execution of the command.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>container</b></td>
+        <td>string</td>
+        <td>
+          Specifies the container.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutafteractionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies the pod.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.afterActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecsutafteractionsindexselector)</sup></sup>
+
+
+
+Specifies the pod.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
+        <td>
+          The matchLabels of the desired pod.<br/>
+          <br/>
+            <i>Default</i>: map[]<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.beforeActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecsut)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b><a href="#benchmarkspecsutbeforeactionsindexexec">exec</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies command to be executed.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutbeforeactionsindexselector">selector</a></b></td>
+        <td>object</td>
+        <td>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.beforeActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecsutbeforeactionsindex)</sup></sup>
+
+
+
+Specifies command to be executed.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>command</b></td>
+        <td>[]string</td>
+        <td>
+          The command to be executed as string array.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>timeoutSeconds</b></td>
+        <td>integer</td>
+        <td>
+          Specifies the timeout (in seconds) for the specified command.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.beforeActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecsutbeforeactionsindex)</sup></sup>
+
+
+
+The selector specifies which resource should be selected for the execution of the command.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>container</b></td>
+        <td>string</td>
+        <td>
+          Specifies the container.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecsutbeforeactionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
+        <td>
+          Specifies the pod.<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.sut.beforeActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecsutbeforeactionsindexselector)</sup></sup>
+
+
+
+Specifies the pod.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
+        <td>
+          The matchLabels of the desired pod.<br/>
+          <br/>
+            <i>Default</i>: map[]<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.loadGenerator.resources[index]
-<sup><sup>[↩ Parent](#benchmarkspecloadgenerator)</sup></sup>
+### benchmark.spec.sut.resources[index]
+<sup><sup>[↩ Parent](#benchmarkspecsut)</sup></sup>
 
 
 
@@ -286,14 +1057,14 @@ The loadGenResourceSets specifies all Kubernetes resources required to start the
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecloadgeneratorresourcesindexconfigmap">configMap</a></b></td>
+        <td><b><a href="#benchmarkspecsutresourcesindexconfigmap">configMap</a></b></td>
         <td>object</td>
         <td>
           The configMap resourceSet loads the Kubernetes manifests from an Kubernetes configMap.<br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b><a href="#benchmarkspecloadgeneratorresourcesindexfilesystem">fileSystem</a></b></td>
+        <td><b><a href="#benchmarkspecsutresourcesindexfilesystem">fileSystem</a></b></td>
         <td>object</td>
         <td>
           The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.<br/>
@@ -303,8 +1074,8 @@ The loadGenResourceSets specifies all Kubernetes resources required to start the
 </table>
 
 
-### benchmark.spec.loadGenerator.resources[index].configMap
-<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorresourcesindex)</sup></sup>
+### benchmark.spec.sut.resources[index].configMap
+<sup><sup>[↩ Parent](#benchmarkspecsutresourcesindex)</sup></sup>
 
 
 
@@ -337,8 +1108,8 @@ The configMap resourceSet loads the Kubernetes manifests from an Kubernetes conf
 </table>
 
 
-### benchmark.spec.loadGenerator.resources[index].fileSystem
-<sup><sup>[↩ Parent](#benchmarkspecloadgeneratorresourcesindex)</sup></sup>
+### benchmark.spec.sut.resources[index].fileSystem
+<sup><sup>[↩ Parent](#benchmarkspecsutresourcesindex)</sup></sup>
 
 
 
@@ -371,12 +1142,12 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
 </table>
 
 
-### benchmark.spec.loadTypes[index]
+### benchmark.spec.infrastructure
 <sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
 
 
 
-
+(Optional) A list of file names that reference Kubernetes resources that are deployed on the cluster to create the required infrastructure.
 
 <table>
     <thead>
@@ -388,25 +1159,38 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecloadtypesindexpatchersindex">patchers</a></b></td>
+        <td><b><a href="#benchmarkspecinfrastructureafteractionsindex">afterActions</a></b></td>
         <td>[]object</td>
         <td>
-          List of patchers used to scale this resource type.<br/>
+          Infrastructure after actions are executed after the teardown of the infrastructure.<br/>
+          <br/>
+            <i>Default</i>: []<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b>typeName</b></td>
-        <td>string</td>
+        <td><b><a href="#benchmarkspecinfrastructurebeforeactionsindex">beforeActions</a></b></td>
+        <td>[]object</td>
         <td>
-          Name of the load type.<br/>
+          Infrastructure before actions are executed before the infrastructure is set up.<br/>
+          <br/>
+            <i>Default</i>: []<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspecinfrastructureresourcesindex">resources</a></b></td>
+        <td>[]object</td>
+        <td>
+          <br/>
+          <br/>
+            <i>Default</i>: []<br/>
+        </td>
+        <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.loadTypes[index].patchers[index]
-<sup><sup>[↩ Parent](#benchmarkspecloadtypesindex)</sup></sup>
+### benchmark.spec.infrastructure.afterActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructure)</sup></sup>
 
 
 
@@ -422,42 +1206,29 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>resource</b></td>
-        <td>string</td>
-        <td>
-          Specifies the Kubernetes resource to be patched.<br/>
-          <br/>
-            <i>Default</i>: <br/>
-        </td>
-        <td>true</td>
-      </tr><tr>
-        <td><b>type</b></td>
-        <td>string</td>
+        <td><b><a href="#benchmarkspecinfrastructureafteractionsindexexec">exec</a></b></td>
+        <td>object</td>
         <td>
-          Type of the Patcher.<br/>
-          <br/>
-            <i>Default</i>: <br/>
+          Specifies command to be executed.<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b>properties</b></td>
-        <td>map[string]string</td>
+        <td><b><a href="#benchmarkspecinfrastructureafteractionsindexselector">selector</a></b></td>
+        <td>object</td>
         <td>
-          (Optional) Patcher specific additional arguments.<br/>
-          <br/>
-            <i>Default</i>: map[]<br/>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.resourceTypes[index]
-<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
-
+### benchmark.spec.infrastructure.afterActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructureafteractionsindex)</sup></sup>
 
 
 
+Specifies command to be executed.
 
 <table>
     <thead>
@@ -469,29 +1240,29 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecresourcetypesindexpatchersindex">patchers</a></b></td>
-        <td>[]object</td>
+        <td><b>command</b></td>
+        <td>[]string</td>
         <td>
-          List of patchers used to scale this resource type.<br/>
+          The command to be executed as string array.<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b>typeName</b></td>
-        <td>string</td>
+        <td><b>timeoutSeconds</b></td>
+        <td>integer</td>
         <td>
-          Name of the resource type.<br/>
+          Specifies the timeout (in seconds) for the specified command.<br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.resourceTypes[index].patchers[index]
-<sup><sup>[↩ Parent](#benchmarkspecresourcetypesindex)</sup></sup>
-
+### benchmark.spec.infrastructure.afterActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructureafteractionsindex)</sup></sup>
 
 
 
+The selector specifies which resource should be selected for the execution of the command.
 
 <table>
     <thead>
@@ -503,42 +1274,31 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>resource</b></td>
-        <td>string</td>
-        <td>
-          Specifies the Kubernetes resource to be patched.<br/>
-          <br/>
-            <i>Default</i>: <br/>
-        </td>
-        <td>true</td>
-      </tr><tr>
-        <td><b>type</b></td>
+        <td><b>container</b></td>
         <td>string</td>
         <td>
-          Type of the patcher.<br/>
+          Specifies the container.<br/>
           <br/>
             <i>Default</i>: <br/>
         </td>
-        <td>true</td>
+        <td>false</td>
       </tr><tr>
-        <td><b>properties</b></td>
-        <td>map[string]string</td>
+        <td><b><a href="#benchmarkspecinfrastructureafteractionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
         <td>
-          (Optional) Patcher specific additional arguments.<br/>
-          <br/>
-            <i>Default</i>: map[]<br/>
+          Specifies the pod.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.sut
-<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+### benchmark.spec.infrastructure.afterActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructureafteractionsindexselector)</sup></sup>
 
 
 
-The appResourceSets specifies all Kubernetes resources required to start the sut. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
+Specifies the pod.
 
 <table>
     <thead>
@@ -550,20 +1310,20 @@ The appResourceSets specifies all Kubernetes resources required to start the sut
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecsutresourcesindex">resources</a></b></td>
-        <td>[]object</td>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
         <td>
+          The matchLabels of the desired pod.<br/>
           <br/>
-          <br/>
-            <i>Default</i>: []<br/>
+            <i>Default</i>: map[]<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.sut.resources[index]
-<sup><sup>[↩ Parent](#benchmarkspecsut)</sup></sup>
+### benchmark.spec.infrastructure.beforeActions[index]
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructure)</sup></sup>
 
 
 
@@ -579,29 +1339,29 @@ The appResourceSets specifies all Kubernetes resources required to start the sut
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecsutresourcesindexconfigmap">configMap</a></b></td>
+        <td><b><a href="#benchmarkspecinfrastructurebeforeactionsindexexec">exec</a></b></td>
         <td>object</td>
         <td>
-          The configMap resourceSet loads the Kubernetes manifests from an Kubernetes configMap.<br/>
+          Specifies command to be executed.<br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b><a href="#benchmarkspecsutresourcesindexfilesystem">fileSystem</a></b></td>
+        <td><b><a href="#benchmarkspecinfrastructurebeforeactionsindexselector">selector</a></b></td>
         <td>object</td>
         <td>
-          The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.<br/>
+          The selector specifies which resource should be selected for the execution of the command.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.sut.resources[index].configMap
-<sup><sup>[↩ Parent](#benchmarkspecsutresourcesindex)</sup></sup>
+### benchmark.spec.infrastructure.beforeActions[index].exec
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructurebeforeactionsindex)</sup></sup>
 
 
 
-The configMap resourceSet loads the Kubernetes manifests from an Kubernetes configMap.
+Specifies command to be executed.
 
 <table>
     <thead>
@@ -613,29 +1373,29 @@ The configMap resourceSet loads the Kubernetes manifests from an Kubernetes conf
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>files</b></td>
+        <td><b>command</b></td>
         <td>[]string</td>
         <td>
-          (Optional) Specifies which files from the configMap should be loaded. If this field is not set, all files are loaded.<br/>
+          The command to be executed as string array.<br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b>name</b></td>
-        <td>string</td>
+        <td><b>timeoutSeconds</b></td>
+        <td>integer</td>
         <td>
-          The name of the configMap<br/>
+          Specifies the timeout (in seconds) for the specified command.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.sut.resources[index].fileSystem
-<sup><sup>[↩ Parent](#benchmarkspecsutresourcesindex)</sup></sup>
+### benchmark.spec.infrastructure.beforeActions[index].selector
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructurebeforeactionsindex)</sup></sup>
 
 
 
-The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
+The selector specifies which resource should be selected for the execution of the command.
 
 <table>
     <thead>
@@ -647,29 +1407,31 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b>files</b></td>
-        <td>[]string</td>
+        <td><b>container</b></td>
+        <td>string</td>
         <td>
-          (Optional) Specifies which files from the configMap should be loaded. If this field is not set, all files are loaded.<br/>
+          Specifies the container.<br/>
+          <br/>
+            <i>Default</i>: <br/>
         </td>
         <td>false</td>
       </tr><tr>
-        <td><b>path</b></td>
-        <td>string</td>
+        <td><b><a href="#benchmarkspecinfrastructurebeforeactionsindexselectorpod">pod</a></b></td>
+        <td>object</td>
         <td>
-          The path to the folder which contains the Kubernetes manifests files.<br/>
+          Specifies the pod.<br/>
         </td>
         <td>false</td>
       </tr></tbody>
 </table>
 
 
-### benchmark.spec.infrastructure
-<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+### benchmark.spec.infrastructure.beforeActions[index].selector.pod
+<sup><sup>[↩ Parent](#benchmarkspecinfrastructurebeforeactionsindexselector)</sup></sup>
 
 
 
-(Optional) A list of file names that reference Kubernetes resources that are deployed on the cluster to create the required infrastructure.
+Specifies the pod.
 
 <table>
     <thead>
@@ -681,12 +1443,12 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
         </tr>
     </thead>
     <tbody><tr>
-        <td><b><a href="#benchmarkspecinfrastructureresourcesindex">resources</a></b></td>
-        <td>[]object</td>
+        <td><b>matchLabels</b></td>
+        <td>map[string]string</td>
         <td>
+          The matchLabels of the desired pod.<br/>
           <br/>
-          <br/>
-            <i>Default</i>: []<br/>
+            <i>Default</i>: map[]<br/>
         </td>
         <td>false</td>
       </tr></tbody>
@@ -795,6 +1557,96 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem.
 </table>
 
 
+### benchmark.spec.kafkaConfig
+<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup>
+
+
+
+Contains the Kafka configuration.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>bootstrapServer</b></td>
+        <td>string</td>
+        <td>
+          The bootstrap servers connection string.<br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b><a href="#benchmarkspeckafkaconfigtopicsindex">topics</a></b></td>
+        <td>[]object</td>
+        <td>
+          List of topics to be created for each experiment. Alternative theodolite offers the possibility to remove certain topics after each experiment.<br/>
+        </td>
+        <td>true</td>
+      </tr></tbody>
+</table>
+
+
+### benchmark.spec.kafkaConfig.topics[index]
+<sup><sup>[↩ Parent](#benchmarkspeckafkaconfig)</sup></sup>
+
+
+
+
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>name</b></td>
+        <td>string</td>
+        <td>
+          The name of the topic.<br/>
+          <br/>
+            <i>Default</i>: <br/>
+        </td>
+        <td>true</td>
+      </tr><tr>
+        <td><b>numPartitions</b></td>
+        <td>integer</td>
+        <td>
+          The number of partitions of the topic.<br/>
+          <br/>
+            <i>Default</i>: 0<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>removeOnly</b></td>
+        <td>boolean</td>
+        <td>
+          Determines if this topic should only be deleted after each experiement. For removeOnly topics the name can be a RegEx describing the topic.<br/>
+          <br/>
+            <i>Default</i>: false<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>replicationFactor</b></td>
+        <td>integer</td>
+        <td>
+          The replication factor of the topic.<br/>
+          <br/>
+            <i>Default</i>: 0<br/>
+        </td>
+        <td>false</td>
+      </tr></tbody>
+</table>
+
+
 ### benchmark.status
 <sup><sup>[↩ Parent](#benchmark)</sup></sup>
 
@@ -1217,10 +2069,19 @@ Specifies the scaling resource that is benchmarked.
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>completionTime</b></td>
+        <td>string</td>
+        <td>
+          Time when this execution was stopped<br/>
+          <br/>
+            <i>Format</i>: date-time<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>executionDuration</b></td>
         <td>string</td>
         <td>
-          Duration of the execution in seconds<br/>
+          Duration of the execution<br/>
         </td>
         <td>false</td>
       </tr><tr>
@@ -1230,5 +2091,14 @@ Specifies the scaling resource that is benchmarked.
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b>startTime</b></td>
+        <td>string</td>
+        <td>
+          Time this execution started<br/>
+          <br/>
+            <i>Format</i>: date-time<br/>
+        </td>
+        <td>false</td>
       </tr></tbody>
 </table>
\ No newline at end of file
diff --git a/docs/creating-a-benchmark.md b/docs/creating-a-benchmark.md
index 122f43b645c9702d16722a3061bfde8bec7c94c6..fde8ba0759407ddea8befc18e244784a9ba34c1f 100644
--- a/docs/creating-a-benchmark.md
+++ b/docs/creating-a-benchmark.md
@@ -108,13 +108,20 @@ Suppose the resources needed by your benchmark are defined as YAML files, locate
 Benchmarks need to specify at least one supported load and resource type for which scalability can be benchmarked.
 
 Load and resource types are described by a name (used for reference from an Execution) and a list of patchers.
+Patchers can be seen as functions, which take a value as input and modify a Kubernetes resource in a patcher-specific way. Examples of patchers are the *ReplicaPatcher*, which modifies the replica specification of a deployment, or the *EnvVarPatcher*, which modifies an environment variable.
+See the [patcher API reference](api-reference/patchers) for an overview of available patchers.
+
 If a benchmark is [executed by an Execution](running-benchmarks), these patchers are used to configure SUT and load generator according to the [load and resource values](creating-an-execution) set in the Execution.
 
 ## Kafka Configuration
 
-Theodolite allows to automatically create and remove Kafka topics for each SLO experiment.
-Use the `removeOnly: True` property for topics which are created automatically by the SUT.
-For those topics, also wildcards are allowed in the topic name.
+Theodolite allows to automatically create and remove Kafka topics for each SLO experiment by setting a `kafkaConfig`.
+It `bootstrapServer` needs to point your Kafka cluster and `topics` configures the list of Kafka topics to be created/removed.
+For each topic, you configure its name, the number of partitions and the replication factor.
+
+With the `removeOnly: True` property, you can also instruct Theodolite to only remove topics and not create them.
+This is useful when benchmarking SUTs, which create topics on their own (e.g., Kafka Streams and Samza applications).
+For those topics, also wildcards are allowed in the topic name and, of course, no partition count or replication factor must be provided.
 
 
 <!-- Further information: API Reference -->
diff --git a/docs/creating-an-execution.md b/docs/creating-an-execution.md
index e70893e7ea4364bfbb30465df95273703ec7f43b..263d630ff2db82927c72d2c2482fcddc09705bfc 100644
--- a/docs/creating-an-execution.md
+++ b/docs/creating-an-execution.md
@@ -58,7 +58,29 @@ As a Benchmark may define multiple supported load and resource types, an Executi
 ## Definition of SLOs
 
 SLOs provide a way to quantify whether a certain load intensity can be handled by a certain amount of provisioned resources.
-An Execution must at least specify one SLO to be checked.
+In Theodolite, SLO are evaluated by requesting monitoring data from Prometheus and analyzing it in a benchmark-specific way.
+An Execution must at least define one SLO to be checked.
+
+A good choice to get started is defining an SLO of type `generic`:
+
+```yaml
+- sloType: "generic"
+  prometheusUrl: "http://prometheus-operated:9090"
+  offset: 0
+  properties:
+    externalSloUrl: "http://localhost:8082"
+    promQLQuery: "sum by(job) (kafka_streams_stream_task_metrics_dropped_records_total>=0)"
+    warmup: 60 # in seconds
+    queryAggregation: max
+    repetitionAggregation: median
+    operator: lte
+    threshold: 1000
+```
+
+All you have to do is to define a [PromQL query](https://prometheus.io/docs/prometheus/latest/querying/basics/) describing which metrics should be requested (`promQLQuery`) and how the resulting time series should be evaluated. With `queryAggregation` you specify how the resulting time series is aggregated to a single value and `repetitionAggregation` describes how the results of multiple repetitions are aggregated. Possible values are
+`mean`, `median`, `mode`, `sum`, `count`, `max`, `min`, `std`, `var`, `skew`, `kurt` as well as percentiles such as `p99` or `p99.9`. The result of aggregation all repetitions is checked against `threshold`. This check is performed using an `operator`, which describes that the result must be "less than" (`lt`), "less than equal" (`lte`), "greater than" (`gt`) or "greater than equal" (`gte`) to the threshold.
+
+In case you need to evaluate monitoring data in a more flexible fashion, you can also change the value of `externalSloUrl` to your custom SLO checker. Have a look at the source code of the [generic SLO checker](https://github.com/cau-se/theodolite/tree/master/slo-checker/generic) to get started.
 
 ## Experimental Setup
 
@@ -72,7 +94,7 @@ The experimental setup can be configured by:
 
 ## Configuration Overrides
 
-In cases where only small modifications of a system under test should be benchmarked, it is not necessarily required to [create a new benchmark](creating-a-benchmark).
+In cases where only small modifications of a system under test should be benchmarked, it is not necessary to [create a new benchmark](creating-a-benchmark).
 Instead, also Executions allow to do small reconfigurations, such as switching on or off a specific Pod scheduler.
 
 This is done by defining `configOverrides` in the Execution. Each override consists of a patcher, defining which Kubernetes resource should be patched in which way, and a value the patcher is applied with.
diff --git a/docs/development/release-process.md b/docs/development/release-process.md
index 59252c9333a513feb59a77b9785e522ed726a814..21f913fbb8626d141d1df49db808fe0b36a01462 100644
--- a/docs/development/release-process.md
+++ b/docs/development/release-process.md
@@ -23,13 +23,15 @@ again be merged into master.
         1. the default `helm/values.yaml` file,
         2. the example `execution/theodolite.yaml` job,
         3. the Kubernetes benchmark resources in `theodolite-benchmarks/definitions/**/resources` and
-        2. the Docker Compose files in `theodolite-benchmarks/docker-test`.
+        4. the Docker Compose files in `theodolite-benchmarks/docker-test`.
 
     2. Update both, the `version` and the `appVersion` fields, in the Helm `Charts.yaml` file to `0.3.1`.
 
-    3. Update `codemeta.json` to match the new version. In particular, make sure that `version` points to the version you are releasing and `dateModified` points to the date you are relasing this version. [CodeMeta generator](https://codemeta.github.io/codemeta-generator/) may help you in updating the file.
+    3. Update the `version` field of the `theodolite/build.gradle` file to `0.3.1`. Make sure to also adjust all references to the build artifact in the `theodolite/README.md`.
 
-    4. Update `CITATION.cff` to match the new version. At least update the `version` field.
+    4. Update `codemeta.json` to match the new version. In particular, make sure that `version` points to the version you are releasing and `dateModified` points to the date you are relasing this version. [CodeMeta generator](https://codemeta.github.io/codemeta-generator/) may help you in updating the file.
+
+    5. Update `CITATION.cff` to match the new version. At least update the `version` field.
 
 4. Create a Helm package by running `./build-package.sh` from the chart directory.
 
@@ -49,8 +51,10 @@ again be merged into master.
 
     1. Update the Helm `Charts.yaml` file to `0.4.0-SNAPSHOT` (see Step 3).
 
-    2. Update the `codemeta.json` file according to Step 3.
+    2. Update the Theodolite `build.gradle` and `README.md` files `0.4.0-SNAPSHOT` (see Step 3).
+
+    3. Update the `codemeta.json` file according to Step 3.
 
-    3. Update the `CITATION.cff` file according to Step 3.
+    4. Update the `CITATION.cff` file according to Step 3.
 
 12. Commit these changes to the `master` branch.
diff --git a/docs/drafts/actions.md b/docs/drafts/actions.md
new file mode 100644
index 0000000000000000000000000000000000000000..8092fddb088b3fe8fc64f51bff03bb0c6504b74f
--- /dev/null
+++ b/docs/drafts/actions.md
@@ -0,0 +1,62 @@
+## Infrastructure
+The necessary infrastructure for an execution can be defined in the benchmark manifests. The related resources are create *before* an execution is started, and removed *after* an execution is finished.
+
+### Example
+
+```yaml
+  infrastructure:
+    resources:
+      - configMap:
+          name: "example-configmap"
+          files:
+            - "uc1-kstreams-deployment.yaml"
+```
+
+## Action Commands
+Theodolite allows to execute commands on running pods (similar to the `kubectl exec -it <pod-name> -- <command>` command). This commands can be run either before (via so called `beforeActions`) or after (via so called `afterActions`) an experiment is executed.
+
+Theodolite checks if all required pods are available for the specified actions (i.e. the pods must either be defined as infrastructure or already deployed in the cluster). If not all pods/resources are available, the benchmark will not be set as `Ready`. Consequently, an action cannot be executed on a pod that is defined as an SUT or loadGen resource.
+
+### Example
+
+```yaml
+# For the system under test
+  sut:
+    resources: ...
+    beforeActions:
+      - selector:
+          pod:
+            matchLabels:
+              app: busybox1
+        exec:
+          command: ["touch", "test-file-sut"]
+          timeoutSeconds: 90
+    afterActions:
+      - selector:
+          pod:
+            matchLabels:
+              app: busybox1
+        exec:
+          command: [ "touch", "test-file-sut-after" ]
+          timeoutSeconds: 90
+
+# analog, for the load generator
+  loadGenerator:
+    resources: ... 
+    beforeActions:
+      - selector:
+          pod:
+            matchLabels:
+              app: busybox1
+        exec:
+          command: ["touch", "test-file-loadGen"]
+          timeoutSeconds: 90
+    afterActions:
+      - selector:
+          pod:
+            matchLabels:
+              app: busybox1
+        exec:
+          command: [ "touch", "test-file-loadGen-after" ]
+          timeoutSeconds: 90
+```
\ No newline at end of file
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..81062e21501bd98a29505433c1e3b43965f5c17d
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/index.yaml b/docs/index.yaml
index 54580ea45f1c678443dae96c7139f53fdac37f60..509844ab0bc371d29302f90f69e769cd52a8e11b 100644
--- a/docs/index.yaml
+++ b/docs/index.yaml
@@ -1,6 +1,146 @@
 apiVersion: v1
 entries:
   theodolite:
+  - apiVersion: v2
+    appVersion: 0.6.3
+    created: "2022-01-24T13:40:40.07330713+01:00"
+    dependencies:
+    - condition: grafana.enabled
+      name: grafana
+      repository: https://grafana.github.io/helm-charts
+      version: 6.17.5
+    - condition: kube-prometheus-stack.enabled
+      name: kube-prometheus-stack
+      repository: https://prometheus-community.github.io/helm-charts
+      version: 20.0.1
+    - condition: cp-helm-charts.enabled
+      name: cp-helm-charts
+      repository: https://soerenhenning.github.io/cp-helm-charts
+      version: 0.6.0
+    - condition: kafka-lag-exporter.enabled
+      name: kafka-lag-exporter
+      repository: https://lightbend.github.io/kafka-lag-exporter/repo/
+      version: 0.6.7
+    description: Theodolite is a framework for benchmarking the horizontal and vertical
+      scalability of cloud-native applications.
+    digest: ebf08e3bf084fcd96eb2ee0588d495258d1741c74019257e55ba40f574874525
+    home: https://www.theodolite.rocks
+    maintainers:
+    - email: soeren.henning@email.uni-kiel.de
+      name: Sören Henning
+      url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc
+    name: theodolite
+    sources:
+    - https://github.com/cau-se/theodolite
+    type: application
+    urls:
+    - https://github.com/cau-se/theodolite/releases/download/v0.6.3/theodolite-0.6.3.tgz
+    version: 0.6.3
+  - apiVersion: v2
+    appVersion: 0.6.2
+    created: "2022-01-23T22:31:04.773793557+01:00"
+    dependencies:
+    - condition: grafana.enabled
+      name: grafana
+      repository: https://grafana.github.io/helm-charts
+      version: 6.17.5
+    - condition: kube-prometheus-stack.enabled
+      name: kube-prometheus-stack
+      repository: https://prometheus-community.github.io/helm-charts
+      version: 20.0.1
+    - condition: cp-helm-charts.enabled
+      name: cp-helm-charts
+      repository: https://soerenhenning.github.io/cp-helm-charts
+      version: 0.6.0
+    - condition: kafka-lag-exporter.enabled
+      name: kafka-lag-exporter
+      repository: https://lightbend.github.io/kafka-lag-exporter/repo/
+      version: 0.6.7
+    description: Theodolite is a framework for benchmarking the horizontal and vertical
+      scalability of cloud-native applications.
+    digest: f6514038741051230dc9be0a6bde3fbc6f92136ecb36c276343e98e550f2c6d0
+    home: https://www.theodolite.rocks
+    maintainers:
+    - email: soeren.henning@email.uni-kiel.de
+      name: Sören Henning
+      url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc
+    name: theodolite
+    sources:
+    - https://github.com/cau-se/theodolite
+    type: application
+    urls:
+    - https://github.com/cau-se/theodolite/releases/download/v0.6.2/theodolite-0.6.2.tgz
+    version: 0.6.2
+  - apiVersion: v2
+    appVersion: 0.6.1
+    created: "2022-01-18T10:40:00.557347616+01:00"
+    dependencies:
+    - condition: grafana.enabled
+      name: grafana
+      repository: https://grafana.github.io/helm-charts
+      version: 6.17.5
+    - condition: kube-prometheus-stack.enabled
+      name: kube-prometheus-stack
+      repository: https://prometheus-community.github.io/helm-charts
+      version: 20.0.1
+    - condition: cp-helm-charts.enabled
+      name: cp-helm-charts
+      repository: https://soerenhenning.github.io/cp-helm-charts
+      version: 0.6.0
+    - condition: kafka-lag-exporter.enabled
+      name: kafka-lag-exporter
+      repository: https://lightbend.github.io/kafka-lag-exporter/repo/
+      version: 0.6.7
+    description: Theodolite is a framework for benchmarking the horizontal and vertical
+      scalability of cloud-native applications.
+    digest: 4896111999375c248d7dda0bdff090c155f464b79416decc0e0b47dc6710b5c7
+    home: https://www.theodolite.rocks
+    maintainers:
+    - email: soeren.henning@email.uni-kiel.de
+      name: Sören Henning
+      url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc
+    name: theodolite
+    sources:
+    - https://github.com/cau-se/theodolite
+    type: application
+    urls:
+    - https://github.com/cau-se/theodolite/releases/download/v0.6.1/theodolite-0.6.1.tgz
+    version: 0.6.1
+  - apiVersion: v2
+    appVersion: 0.6.0
+    created: "2022-01-12T13:53:08.413006558+01:00"
+    dependencies:
+    - condition: grafana.enabled
+      name: grafana
+      repository: https://grafana.github.io/helm-charts
+      version: 6.17.5
+    - condition: kube-prometheus-stack.enabled
+      name: kube-prometheus-stack
+      repository: https://prometheus-community.github.io/helm-charts
+      version: 20.0.1
+    - condition: cp-helm-charts.enabled
+      name: cp-helm-charts
+      repository: https://soerenhenning.github.io/cp-helm-charts
+      version: 0.6.0
+    - condition: kafka-lag-exporter.enabled
+      name: kafka-lag-exporter
+      repository: https://lightbend.github.io/kafka-lag-exporter/repo/
+      version: 0.6.7
+    description: Theodolite is a framework for benchmarking the horizontal and vertical
+      scalability of cloud-native applications.
+    digest: 53435304229582680d55360ad79a25050f6cc97641cbb88d691b35d91a54d354
+    home: https://www.theodolite.rocks
+    maintainers:
+    - email: soeren.henning@email.uni-kiel.de
+      name: Sören Henning
+      url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc
+    name: theodolite
+    sources:
+    - https://github.com/cau-se/theodolite
+    type: application
+    urls:
+    - https://github.com/cau-se/theodolite/releases/download/v0.6.0/theodolite-0.6.0.tgz
+    version: 0.6.0
   - apiVersion: v2
     appVersion: 0.5.1
     created: "2021-11-12T16:15:01.629937292+01:00"
@@ -106,4 +246,4 @@ entries:
     urls:
     - https://github.com/cau-se/theodolite/releases/download/v0.4.0/theodolite-0.4.0.tgz
     version: 0.4.0
-generated: "2021-11-12T16:15:01.591258889+01:00"
+generated: "2022-01-24T13:40:40.036786105+01:00"
diff --git a/docs/running-benchmarks.md b/docs/running-benchmarks.md
index eda817d28b6a10b2e2f33e6986a3b018e089beff..0a76316c0515233f9445b363f941d60ab7aa0e06 100644
--- a/docs/running-benchmarks.md
+++ b/docs/running-benchmarks.md
@@ -11,6 +11,7 @@ Running scalability benchmarks with Theodolite involves the following steps:
 1. [Deploying a benchmark to Kubernetes](#deploying-a-benchmark)
 1. [Creating an execution](#creating-an-execution), which describes the experimental setup for running the benchmark
 1. [Accessing benchmark results](#accessing-benchmark-results)
+1. [Analyzing benchmark results](#analyzing-benchmark-results) with Theodolite's Jupyter notebooks
 
 
 ## Deploying a Benchmark
@@ -131,3 +132,32 @@ For installations without persistence, but also as an alternative for installati
 ```sh
 kubectl cp $(kubectl get pod -l app=theodolite -o jsonpath="{.items[0].metadata.name}"):/results . -c results-access
 ```
+
+## Analyzing Benchmark Results
+
+Theodolite comes with Jupyter notebooks for analyzing and visualizing benchmark execution results.
+The easiest way to use them is at MyBinder:
+
+[Launch Notebooks](https://mybinder.org/v2/gh/cau-se/theodolite/HEAD?labpath=analysis){: .btn .btn-primary }
+{: .text-center }
+
+Alternatively, you can also [run these notebook locally](https://github.com/cau-se/theodolite/tree/master/analysis), for example, with Docker or Visual Studio Code.
+
+The notebooks allow to compute a scalability function using Theodolite's *demand* metric and to visualize multiple such functions in plots:
+
+### Computing the *demand* metric with `demand-metric.ipynb` (optional)
+
+After finishing a benchmark execution, Theodolite creates a `exp<id>_demand.csv` file. It maps the tested load intensities to the minimal required resources for that load. If the monitoring data collected during benchmark execution should be analyzed in more detail, the `demand-metric.ipynb` notebook can be used. 
+
+Theodolite stores monitoring data for each conducted SLO experiment in `exp<id>_<load>_<resources>_<slo-slug>_<rep>.csv` files, where `<id>` is the ID of an execution, `<load>` the corresponding load intensity value, `<resources>` the resources value, `<slo-slug>` the [name of the SLO](creating-an-execution.html#definition-of-slos) and `<rep>` the repetition counter.
+The `demand-metric.ipynb` notebook reads these files and generates a new CSV file mapping load intensities to the minimal required resources. The format of this file corresponds to the original `exp<id>_demand.csv` file created when running the benchmark, but allows, for example, to evaluate different warm-up periods.
+
+Currently, the `demand-metric.ipynb` notebook only supports benchmarks with the *lag trend SLO* out-of-the-box, but can easily be adjusted to perform any other type of analysis.
+
+### Plotting benchmark results with the *demand* metric with `demand-metric-plot.ipynb`
+
+The `demand-metric-plot.ipynb` takes one or multiple `exp<id>_demand.csv` files as input and visualize them together in a plot.
+Input files can either be taken directly from Theodolite, or created from the `demand-metric.ipynb` notebooks.
+
+All plotting code is only intended to serve as a template. Adjust it as needed to change colors, labels, formatting, etc. as needed. 
+Please refer to the official docs of [MatPlotLib](https://matplotlib.org/) and the [ggplot](https://matplotlib.org/stable/gallery/style_sheets/ggplot.html) style, which are used to generate the plots.
diff --git a/docs/theodolite-benchmarks/index.md b/docs/theodolite-benchmarks/index.md
index 9b08e6f5f2fe049c17dce819b7c4d9b83fcbc12e..30b8e816ef1b48e770c8e42be1d599a71431c976 100644
--- a/docs/theodolite-benchmarks/index.md
+++ b/docs/theodolite-benchmarks/index.md
@@ -1,12 +1,12 @@
 ---
 title: Available Benchmarks
-has_children: false
+has_children: true
 nav_order: 7
 ---
 
 # Theodolite Benchmarks
 
-Theodolite comes with 4 application benchmarks, which are based on typical use cases for stream processing within microservices. For each benchmark, a corresponding load generator is provided. Currently, Theodolite provides benchmark implementations for Apache Kafka Streams and Apache Flink.
+Theodolite comes with 4 application benchmarks, which are based on typical use cases for stream processing within microservices. For each benchmark, a corresponding [load generator](load-generator) is provided. Currently, Theodolite provides benchmark implementations for Apache Kafka Streams and Apache Flink.
 
 
 Theodolite's benchmarks are based on typical use cases for stream processing within microservices. Specifically, all benchmarks represent some sort of microservice doing Industrial Internet of Things data analytics. 
diff --git a/docs/theodolite-benchmarks/load-generator.md b/docs/theodolite-benchmarks/load-generator.md
new file mode 100644
index 0000000000000000000000000000000000000000..6d42ea06d9cb008a9aeddcc8145a2868c8d916b1
--- /dev/null
+++ b/docs/theodolite-benchmarks/load-generator.md
@@ -0,0 +1,87 @@
+---
+title: Load Generators
+parent: Available Benchmarks
+has_children: false
+nav_order: 1
+---
+
+# Load Generator Framework
+
+Theodolite's benchmarks come with a flexible load generator framework. It is used to create load on the [4 Theodolite benchmarks](#prebuilt-container-images), but can also be applied to create [custom load generators](#creating-a-custom-load-generator).
+It is particularly designed for scalability: Just spin up multiple instances of the load generator and the instances automatically divide the load to be generated among themselves.
+
+## Prebuilt container images
+
+For each benchmark, we provide a [load generator as OCI container image](https://github.com/orgs/cau-se/packages?tab=packages&q=workload-generator). These load generators simulate smart power meters in an industrial facility, which generate measurement records at a fixed rate. Records are published to an Apache Kafka topic (default) or sent as POST requests to an HTTP endpoint.
+
+You can simply run a load generator container, for example, for benchmark UC1 with:
+
+```sh
+docker run ghcr.io/cau-se/theodolite-uc1-workload-generator
+```
+
+### Message format
+
+Messages generated by the load generators represent a single measurement of [active power](https://en.wikipedia.org/wiki/AC_power#Active,_reactive,_apparent,_and_complex_power_in_sinusoidal_steady-state). The corresponding message type is specified as [`ActivePowerRecords`](https://github.com/cau-se/titan-ccp-common/blob/master/src/main/avro/ActivePower.avdl)
+defined with Avro. It consists of an identifier for simulated power sensor, a timestamp in epoch milliseconds and the actual measured (simulated) value in watts.
+
+When sending generated records via Apache Kafka, these records are serialized with the [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry).
+If the load generator is configured to send records as HTTP POST requests, records are serialized as JSON according to the following format:
+
+```json
+{
+  "identifier": "sensor-id",
+  "timestamp": 1645564942000,
+  "valueInW": 1234.56
+}
+```
+
+### Configuration
+
+The prebuilt container images can be configured with the following environment variables:
+
+| Environment Variable | Description | Default |
+|:----|:----|:----|
+| `BOOTSTRAP_SERVER` | Address (`hostname:port`) of another load generator instance to form a cluster with. Can also be this instance. | `localhost:5701` |
+| `KUBERNETES_DNS_NAME` | Kubernetes service name to discover other load generators to form a cluster with. Must be a fully qualified domain name (FQDN), e.g., something like `<service>.<namespace>.svc.cluster.local`. * Requires `BOOTSTRAP_SERVER` not to be set. | |
+| `PORT` | Port used for for coordination among load generator instances. | 5701 |
+| `PORT_AUTO_INCREMENT` | If set to true and the specified PORT is already used, use the next higher one. Useful if multiple instances should run on the same host, without configuring each instance individually. | true |
+| `CLUSTER_NAME_PREFIX` | Only required if unrelated load generators form a cluster. | theodolite-load-generation |
+| `TARGET` | The target system the load generator send messages to. Valid values are: `kafka`, `http`. | `kafka` |
+| `KAFKA_BOOTSTRAP_SERVERS` | A list of host/port pairs to use for establishing the initial connection to the Kafka cluster. See [Kafka producer config: `bootstrap.servers`](https://kafka.apache.org/documentation/#producerconfigs_bootstrap.servers) for more information. Only used if Kafka is set as `TARGET`. | `localhost:9092` |
+| `KAFKA_INPUT_TOPIC` | Name of the Kafka topic, which should receive the generated messages. Only used if Kafka is set as `TARGET`. | input |
+| `SCHEMA_REGISTRY_URL` | URL of the [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry). | `http://localhost:8081` |
+| `KAFKA_BATCH_SIZE` | Value for the Kafka producer configuration: [`batch.size`](https://kafka.apache.org/documentation/#producerconfigs_batch.size). Only used if Kafka is set as `TARGET`. | see Kafka producer config: [`batch.size`](https://kafka.apache.org/documentation/#producerconfigs_batch.size) |
+| `KAFKA_LINGER_MS` | Value for the Kafka producer configuration: [`linger.ms`](https://kafka.apache.org/documentation/#producerconfigs_linger.ms). Only used if Kafka is set as `TARGET`. | see Kafka producer config: [`linger.ms`](https://kafka.apache.org/documentation/#producerconfigs_linger.ms) |
+| `KAFKA_BUFFER_MEMORY` | Value for the Kafka producer configuration: [`buffer.memory`](https://kafka.apache.org/documentation/#producerconfigs_buffer.memory) Only used if Kafka is set as `TARGET`. | see Kafka producer config: [`buffer.memory`](https://kafka.apache.org/documentation/#producerconfigs_buffer.memory) |
+| `HTTP_URL` | The URL the load generator should post messages to. Only used if HTTP is set as `TARGET`. | |
+| `NUM_SENSORS` | The amount of simulated sensors. | 10 |
+| `PERIOD_MS` | The time in milliseconds between generating two messages for the same sensor. With our Theodolite benchmarks, we apply an [open workload model](https://www.usenix.org/legacy/event/nsdi06/tech/full_papers/schroeder/schroeder.pdf) in which new messages are generated at a fixed rate, without considering the think time of the target server nor the time required for generating a message. | 1000 |
+| `VALUE` | The constant `valueInW` of an `ActivePowerRecord`. | 10 |
+| `THREADS` | Number of worker threads used to generate the load. | 4 |
+
+Please note that there are some additional configuration options for benchmark [UC4's load generator](https://github.com/cau-se/theodolite/blob/master/theodolite-benchmarks/uc4-load-generator/src/main/java/theodolite/uc4/workloadgenerator/LoadGenerator.java).
+
+## Creating a custom load generator
+
+To create a custom load generator, you need to import the [load-generator-commons](https://github.com/cau-se/theodolite/tree/master/theodolite-benchmarks/load-generator-commons) project. You can then create an instance of the `LoadGenerator` object and call its `run` method:
+
+```java
+LoadGenerator loadGenerator = new LoadGenerator()
+    .setClusterConfig(clusterConfig)
+    .setLoadDefinition(new WorkloadDefinition(
+        new KeySpace(key_prefix, numSensors),
+        duration))
+    .setGeneratorConfig(new LoadGeneratorConfig(
+        recordGenerator,
+        recordSender))
+    .withThreads(threads);
+loadGenerator.run();
+```
+
+Alternatively, you can also start with a load generator populated with a default configuration or created from environment variables and then adjust the `LoadGenerator` as desired:
+
+```java
+LoadGenerator loadGeneratorFromDefaults = LoadGenerator.fromDefaults()
+LoadGenerator loadGeneratorFromEnv = LoadGenerator.fromEnvironment();
+```
diff --git a/execution/theodolite.yaml b/execution/theodolite.yaml
index ae18a68ee61c71e20008a71537357cdf9521216a..495b98f8dfff7fb5ddfe95d71d09fc1dfff67e0e 100644
--- a/execution/theodolite.yaml
+++ b/execution/theodolite.yaml
@@ -21,17 +21,16 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: metadata.namespace
-
             # - name: MODE
             #   value: yaml-executor # Default is `yaml-executor`
             - name: THEODOLITE_EXECUTION
-              value: "execution/execution.yaml" # The name of this file must correspond to the filename of the execution, from which the config map is created.
+              value: "/deployments/execution/execution.yaml" # The name of this file must correspond to the filename of the execution, from which the config map is created.
             - name: THEODOLITE_BENCHMARK
-              value: "benchmark/benchmark.yaml" # The name of this file must correspond to the filename of the benchmark, from which the config map is created.
+              value: "/deployments/benchmark/benchmark.yaml" # The name of this file must correspond to the filename of the benchmark, from which the config map is created.
             - name: THEODOLITE_APP_RESOURCES
-              value: "benchmark-resources"
+              value: "/deployments/benchmark-resources"
             - name: RESULTS_FOLDER # Folder for saving results
-              value: results # Default is the pwd (/deployments)
+              value: /deployments/results # Default is the pwd (/deployments)
             # - name: CREATE_RESULTS_FOLDER # Specify whether the specified result folder should be created if it does not exist.
             #   value: "false" # Default is false.
           volumeMounts:
diff --git a/helm/Chart.yaml b/helm/Chart.yaml
index 856644feb587fd1783162d55a7e6ee8c596c0ac0..27451ad55ce75592db9dc7550b1f81dced3951bc 100644
--- a/helm/Chart.yaml
+++ b/helm/Chart.yaml
@@ -1,7 +1,7 @@
 apiVersion: v2
 name: theodolite
-description: Theodolite is a framework for benchmarking the scalability of stream processing engines.
-home: https://cau-se.github.io/theodolite
+description: Theodolite is a framework for benchmarking the horizontal and vertical scalability of cloud-native applications.
+home: https://www.theodolite.rocks
 sources:
   - https://github.com/cau-se/theodolite
 maintainers:
@@ -29,6 +29,6 @@ dependencies:
     repository: https://lightbend.github.io/kafka-lag-exporter/repo/
     condition: kafka-lag-exporter.enabled
 
-version: 0.6.0-SNAPSHOT
+version: 0.7.0-SNAPSHOT
 
-appVersion: 0.6.0-SNAPSHOT
+appVersion: 0.7.0-SNAPSHOT
diff --git a/helm/preconfigs/minimal.yaml b/helm/preconfigs/minimal.yaml
index b0828c2f424e8456933dc626a66a199cd60aa5da..80a83f06cc9838e01f812e730932b9b79d947161 100644
--- a/helm/preconfigs/minimal.yaml
+++ b/helm/preconfigs/minimal.yaml
@@ -8,5 +8,8 @@ cp-helm-charts:
       offsets.topic.replication.factor: "1"
 
 operator:
+  sloChecker:
+    droppedRecordsKStreams:
+      enabled: false
   resultsVolume:
     enabled: false
diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt
index ef1eea71080f55d08e193b9741327189865fa3dd..aabcc41cfba4844d29b94317af11bfb9ab9babcb 100644
--- a/helm/templates/NOTES.txt
+++ b/helm/templates/NOTES.txt
@@ -1,3 +1,3 @@
-Welcome to Theodolite!
+Welcome to Theodolite, a framework for benchmarking the scalability of cloud-native applications!
 
-Visit https://cau-se.github.io/theodolite for getting started and more information.
+Visit https://www.theodolite.rocks for getting started and more information.
diff --git a/helm/templates/prometheus/datasource-config-map.yaml b/helm/templates/grafana/datasource-config-map.yaml
similarity index 100%
rename from helm/templates/prometheus/datasource-config-map.yaml
rename to helm/templates/grafana/datasource-config-map.yaml
diff --git a/helm/templates/prometheus/prometheus.yaml b/helm/templates/prometheus/prometheus.yaml
index 4e297b20290be9686b901fa8c76823136c6fabef..23a015250e19cc14550ce73e8162ba27f65be774 100644
--- a/helm/templates/prometheus/prometheus.yaml
+++ b/helm/templates/prometheus/prometheus.yaml
@@ -5,10 +5,7 @@ metadata:
   name: {{ template "theodolite.fullname" . }}-prometheus
 spec:
   serviceAccountName: {{ template "theodolite.fullname" . }}-prometheus
-  serviceMonitorSelector:
-    matchLabels:
-      #app: cp-kafka
-      appScope: titan-ccp
+  serviceMonitorSelector: {}
   resources:
     requests:
       memory: 400Mi
diff --git a/helm/templates/theodolite/theodolite-operator.yaml b/helm/templates/theodolite/theodolite-operator.yaml
index c7ced880cbbfbb9795ef59156ea1df7d5b860ec6..f2669686eada049d33c5c88169d8d2ec3af84261 100644
--- a/helm/templates/theodolite/theodolite-operator.yaml
+++ b/helm/templates/theodolite/theodolite-operator.yaml
@@ -27,10 +27,37 @@ spec:
             - name: MODE
               value: operator
             - name: RESULTS_FOLDER
-              value: "./results"
+              value: "/deployments/results"
           volumeMounts:
             - name: theodolite-results-volume
               mountPath: "/deployments/results"
+          resources:
+            requests:
+              memory: "512Mi"
+              cpu: "250m"
+            limits:
+              memory: "1024Mi"
+              cpu: "500m"
+        {{- if .Values.operator.sloChecker.generic.enabled }}
+        - name: slo-checker-generic
+          image: "{{ .Values.operator.sloChecker.generic.image }}:{{ .Values.operator.sloChecker.generic.imageTag }}"
+          imagePullPolicy: "{{ .Values.operator.sloChecker.generic.imagePullPolicy }}"
+          ports:
+          - containerPort: 8082
+            name: analysis
+          env:
+          - name: PORT
+            value: "8082"
+          - name: LOG_LEVEL
+            value: INFO
+          resources:
+            requests:
+              memory: "64Mi"
+              cpu: "50m"
+            limits:
+              memory: "128Mi"
+              cpu: "100m"
+        {{- end }}
         {{- if .Values.operator.sloChecker.lagTrend.enabled }}
         - name: lag-trend-slo-checker
           image: "{{ .Values.operator.sloChecker.lagTrend.image }}:{{ .Values.operator.sloChecker.lagTrend.imageTag }}"
@@ -41,6 +68,13 @@ spec:
           env:
           - name: LOG_LEVEL
             value: INFO
+          resources:
+            requests:
+              memory: "64Mi"
+              cpu: "50m"
+            limits:
+              memory: "128Mi"
+              cpu: "100m"
         {{- end }}
         {{- if .Values.operator.sloChecker.droppedRecordsKStreams.enabled }}
         - name: slo-checker-dropped-records-kstreams
@@ -54,6 +88,13 @@ spec:
             value: "8081"
           - name: LOG_LEVEL
             value: INFO
+          resources:
+            requests:
+              memory: "64Mi"
+              cpu: "50m"
+            limits:
+              memory: "128Mi"
+              cpu: "100m"
         {{- end }}
         {{- if .Values.operator.resultsVolume.accessSidecar.enabled }}
         - name: results-access
diff --git a/helm/values.yaml b/helm/values.yaml
index 1e57b42c485eb20a5525f25cfc0ef616e65a325c..ba58b040974886518ab111d668cb0db1140b2eb8 100644
--- a/helm/values.yaml
+++ b/helm/values.yaml
@@ -256,6 +256,11 @@ operator:
   nodeSelector: {}
 
   sloChecker:
+    generic:
+      enabled: true
+      image: ghcr.io/cau-se/theodolite-slo-checker-generic
+      imageTag: latest
+      imagePullPolicy: Always
     lagTrend:
       enabled: true
       image: ghcr.io/cau-se/theodolite-slo-checker-lag-trend
diff --git a/slo-checker/dropped-records/Dockerfile b/slo-checker/dropped-records/Dockerfile
index 032b8153a6989ca04631ba553289dacb3620a38d..2cbc89a150217f15b3c4ba921050db720a34bf50 100644
--- a/slo-checker/dropped-records/Dockerfile
+++ b/slo-checker/dropped-records/Dockerfile
@@ -1,6 +1,15 @@
-FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
+FROM python:3.8
 
-COPY requirements.txt requirements.txt
-RUN pip install -r requirements.txt
+WORKDIR /code
 
-COPY ./app /app
\ No newline at end of file
+COPY ./requirements.txt /code/requirements.txt
+RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
+
+COPY ./app /code/app
+
+WORKDIR /code/app
+
+ENV HOST 0.0.0.0
+ENV PORT 80
+
+CMD ["sh", "-c", "uvicorn main:app --host $HOST --port $PORT"]
diff --git a/slo-checker/dropped-records/requirements.txt b/slo-checker/dropped-records/requirements.txt
index 8b6c3863226c2bd5e8bcd7982b2674dee593f192..a3d5ff675d6a89b2514f1936b1a8104d13ad9b55 100644
--- a/slo-checker/dropped-records/requirements.txt
+++ b/slo-checker/dropped-records/requirements.txt
@@ -1,5 +1,6 @@
-fastapi==0.65.2
-scikit-learn==0.20.3
-pandas==1.0.3
-uvicorn
 requests
+fastapi>=0.68.0,<0.69.0
+uvicorn>=0.15.0,<0.16.0
+#pydantic>=1.8.0,<2.0.0
+#scikit-learn==0.22.2
+pandas==1.0.3
diff --git a/slo-checker/generic/Dockerfile b/slo-checker/generic/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2cbc89a150217f15b3c4ba921050db720a34bf50
--- /dev/null
+++ b/slo-checker/generic/Dockerfile
@@ -0,0 +1,15 @@
+FROM python:3.8
+
+WORKDIR /code
+
+COPY ./requirements.txt /code/requirements.txt
+RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
+
+COPY ./app /code/app
+
+WORKDIR /code/app
+
+ENV HOST 0.0.0.0
+ENV PORT 80
+
+CMD ["sh", "-c", "uvicorn main:app --host $HOST --port $PORT"]
diff --git a/slo-checker/generic/README.md b/slo-checker/generic/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a1358a06dc4165c678bca8745dd40473a7c5880
--- /dev/null
+++ b/slo-checker/generic/README.md
@@ -0,0 +1,89 @@
+# Generic SLO Evaluator
+
+## Execution
+
+For development:
+
+```sh
+uvicorn main:app --reload
+```
+
+## Build the docker image:
+
+```sh
+docker build . -t theodolite-evaluator
+```
+
+Run the Docker image:
+
+```sh
+docker run -p 80:80 theodolite-evaluator
+```
+
+## Configuration
+
+You can set the `HOST` and the `PORT` (and a lot of more parameters) via environment variables. Default is `0.0.0.0:80`.
+For more information see the [Gunicorn/FastAPI Docker docs](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#advanced-usage).
+
+## API Documentation
+
+The running webserver provides a REST API with the following route:
+
+* /
+  * Method: POST
+  * Body:
+    * results
+      * metric-metadata
+      * values
+    * metadata
+      * warmup
+      * queryAggregation
+      * repetitionAggregation
+      * operator
+      * threshold
+
+The body of the request must be a JSON string that satisfies the following conditions:
+
+* **dropped records**: This property is based on the [Range Vector type](https://www.prometheus.io/docs/prometheus/latest/querying/api/#range-vectors) from Prometheus and must have the following JSON *structure*:
+
+    ```json
+    {
+        "results": [
+            [
+                {
+                    "metric": {
+                        "<label-name>": "<label-value>"
+                    },
+                    "values": [
+                        [
+                            <unix_timestamp>, // 1.634624989695E9
+                            "<sample_value>" // integer
+                        ]
+                    ]
+                }
+            ]
+        ],
+        "metadata": {
+            "warmup": 60,
+            "queryAggregation": "max",
+            "repetitionAggregation": "median",
+            "operator": "lt",
+            "threshold": 2000000
+        }
+    }
+    ```
+
+### description
+
+* results:
+  * metric-metadata:
+    * Labels of this metric. The `generic` slo checker does not use labels in the calculation of the service level objective.
+  * results
+    * The `<unix_timestamp>` provided as the first element of each element in the "values" array must be the timestamp of the measurement value in seconds (with optional decimal precision)
+    * The `<sample_value>` must be the measurement value as string.
+* metadata: For the calculation of the service level objective require metadata.
+  * **warmup**: Specifies the warmup time in seconds that are ignored for evaluating the SLO.
+  * **queryAggregation**: Specifies the function used to aggregate a query. 
+  * **repetitionAggregation**: Specifies the function used to aggregate a the results of multiple query aggregations.
+  * **operator**: Specifies how the result should be checked agains a threshold. Possible values are `lt`, `lte`, `gt` and `gte`.
+  * **threshold**: Must be an unsigned integer that specifies the threshold for the SLO evaluation.
diff --git a/slo-checker/generic/app/main.py b/slo-checker/generic/app/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f36c8739da00128ad94feb1f2d7871df7e2ff137
--- /dev/null
+++ b/slo-checker/generic/app/main.py
@@ -0,0 +1,72 @@
+from fastapi import FastAPI,Request
+import logging
+import os
+import json
+import sys
+import re
+import pandas as pd
+
+
+app = FastAPI()
+
+logging.basicConfig(stream=sys.stdout,
+                    format="%(asctime)s %(levelname)s %(name)s: %(message)s")
+logger = logging.getLogger("API")
+
+
+if os.getenv('LOG_LEVEL') == 'INFO':
+    logger.setLevel(logging.INFO)
+elif os.getenv('LOG_LEVEL') == 'WARNING':
+    logger.setLevel(logging.WARNING)
+elif os.getenv('LOG_LEVEL') == 'DEBUG':
+    logger.setLevel(logging.DEBUG)
+
+
+def get_aggr_func(func_string: str):
+    if func_string in ['mean', 'median', 'mode', 'sum', 'count', 'max', 'min', 'std', 'var', 'skew', 'kurt']:
+        return func_string
+    elif re.search(r'^p\d\d?(\.\d+)?$', func_string): # matches strings like 'p99', 'p99.99', 'p1', 'p0.001'
+        def percentile(x):
+            return x.quantile(float(func_string[1:]) / 100)
+        percentile.__name__ = func_string
+        return percentile
+    else:
+        raise ValueError('Invalid function string.')
+
+def aggr_query(values: dict, warmup: int, aggr_func):
+    df = pd.DataFrame.from_dict(values)
+    df.columns = ['timestamp', 'value']
+    filtered = df[df['timestamp'] >= (df['timestamp'][0] + warmup)]
+    filtered['value'] = filtered['value'].astype(int)
+    return filtered['value'].aggregate(aggr_func)
+
+def check_result(result, operator: str, threshold):
+    if operator == 'lt':
+        return result < threshold
+    if operator == 'lte':
+        return result <= threshold
+    if operator == 'gt':
+        return result > threshold
+    if operator == 'gte':
+        return result >= threshold
+    else:
+        raise ValueError('Invalid operator string.')
+
+
+
+@app.post("/",response_model=bool)
+async def check_slo(request: Request):
+    data = json.loads(await request.body())
+    logger.info('Received request with metadata: %s', data['metadata'])
+
+    warmup = int(data['metadata']['warmup'])
+    query_aggregation = get_aggr_func(data['metadata']['queryAggregation'])
+    rep_aggregation = get_aggr_func(data['metadata']['repetitionAggregation'])
+    operator = data['metadata']['operator']
+    threshold = int(data['metadata']['threshold'])
+
+    query_results = [aggr_query(r[0]["values"], warmup, query_aggregation) for r in data["results"]]
+    result = pd.DataFrame(query_results).aggregate(rep_aggregation).at[0]
+    return check_result(result, operator, threshold)
+
+logger.info("SLO evaluator is online")
\ No newline at end of file
diff --git a/slo-checker/generic/app/test.py b/slo-checker/generic/app/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2609225ddc9e6e96cdcd01db197cebbdd6501102
--- /dev/null
+++ b/slo-checker/generic/app/test.py
@@ -0,0 +1,56 @@
+import unittest
+from main import app, get_aggr_func, check_result
+import json
+from fastapi.testclient import TestClient
+
+class TestSloEvaluation(unittest.TestCase):
+    client = TestClient(app)
+
+    def test_1_rep(self):
+        with open('../resources/test-1-rep-success.json') as json_file:
+            data = json.load(json_file)
+            response = self.client.post("/", json=data)
+            self.assertEqual(response.json(), True)
+
+    def test_get_aggr_func_mean(self):
+        self.assertEqual(get_aggr_func('median'), 'median')
+    
+    def test_get_aggr_func_p99(self):
+        self.assertTrue(callable(get_aggr_func('p99')))
+
+    def test_get_aggr_func_p99_9(self):
+        self.assertTrue(callable(get_aggr_func('p99.9')))
+
+    def test_get_aggr_func_p99_99(self):
+        self.assertTrue(callable(get_aggr_func('p99.99')))
+
+    def test_get_aggr_func_p0_1(self):
+        self.assertTrue(callable(get_aggr_func('p0.1')))
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'p99.')
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'q99')
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'mux')
+    
+    def test_check_result_lt(self):
+        self.assertEqual(check_result(100, 'lt', 200), True)
+        
+    def test_check_result_lte(self):
+        self.assertEqual(check_result(200, 'lte', 200), True)
+    
+    def test_check_result_gt(self):
+        self.assertEqual(check_result(100, 'gt', 200), False)
+
+    def test_check_result_gte(self):
+        self.assertEqual(check_result(300, 'gte', 200), True)
+
+    def test_check_result_invalid(self):
+        self.assertRaises(ValueError, check_result, 100, 'xyz', 200)
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/slo-checker/generic/requirements.txt b/slo-checker/generic/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a3d5ff675d6a89b2514f1936b1a8104d13ad9b55
--- /dev/null
+++ b/slo-checker/generic/requirements.txt
@@ -0,0 +1,6 @@
+requests
+fastapi>=0.68.0,<0.69.0
+uvicorn>=0.15.0,<0.16.0
+#pydantic>=1.8.0,<2.0.0
+#scikit-learn==0.22.2
+pandas==1.0.3
diff --git a/slo-checker/generic/resources/test-1-rep-success.json b/slo-checker/generic/resources/test-1-rep-success.json
new file mode 100644
index 0000000000000000000000000000000000000000..b70f461cf620d8eee8c4d9d93feb46db7498626f
--- /dev/null
+++ b/slo-checker/generic/resources/test-1-rep-success.json
@@ -0,0 +1,276 @@
+{
+    "results": [
+        [
+            {
+                "metric": {
+                    "job": "titan-ccp-aggregation"
+                },
+                "values": [
+                    [
+                        1.634624674695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624679695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624684695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624689695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624694695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624699695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624704695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624709695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624714695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624719695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624724695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624729695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624734695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624739695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624744695E9,
+                        "1"
+                    ],
+                    [
+                        1.634624749695E9,
+                        "3"
+                    ],
+                    [
+                        1.634624754695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624759695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624764695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624769695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624774695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624779695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624784695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624789695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624794695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624799695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624804695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624809695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624814695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624819695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624824695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624829695E9,
+                        "159524"
+                    ],
+                    [
+                        1.634624834695E9,
+                        "209870"
+                    ],
+                    [
+                        1.634624839695E9,
+                        "278597"
+                    ],
+                    [
+                        1.634624844695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624849695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624854695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624859695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624864695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624869695E9,
+                        "606893"
+                    ],
+                    [
+                        1.634624874695E9,
+                        "653534"
+                    ],
+                    [
+                        1.634624879695E9,
+                        "755796"
+                    ],
+                    [
+                        1.634624884695E9,
+                        "919317"
+                    ],
+                    [
+                        1.634624889695E9,
+                        "919317"
+                    ],
+                    [
+                        1.634624894695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624899695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624904695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624909695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624914695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624919695E9,
+                        "1036530"
+                    ],
+                    [
+                        1.634624924695E9,
+                        "1078477"
+                    ],
+                    [
+                        1.634624929695E9,
+                        "1194775"
+                    ],
+                    [
+                        1.634624934695E9,
+                        "1347755"
+                    ],
+                    [
+                        1.634624939695E9,
+                        "1352151"
+                    ],
+                    [
+                        1.634624944695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624949695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624954695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624959695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624964695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624969695E9,
+                        "1525685"
+                    ],
+                    [
+                        1.634624974695E9,
+                        "1689296"
+                    ],
+                    [
+                        1.634624979695E9,
+                        "1771358"
+                    ],
+                    [
+                        1.634624984695E9,
+                        "1854284"
+                    ],
+                    [
+                        1.634624989695E9,
+                        "1854284"
+                    ]
+                ]
+            }
+        ]
+    ],
+    "metadata": {
+        "warmup": 60,
+        "queryAggregation": "max",
+        "repetitionAggregation": "median",
+        "operator": "lt",
+        "threshold": 2000000
+    }
+}
\ No newline at end of file
diff --git a/slo-checker/record-lag/Dockerfile b/slo-checker/record-lag/Dockerfile
index 032b8153a6989ca04631ba553289dacb3620a38d..2cbc89a150217f15b3c4ba921050db720a34bf50 100644
--- a/slo-checker/record-lag/Dockerfile
+++ b/slo-checker/record-lag/Dockerfile
@@ -1,6 +1,15 @@
-FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
+FROM python:3.8
 
-COPY requirements.txt requirements.txt
-RUN pip install -r requirements.txt
+WORKDIR /code
 
-COPY ./app /app
\ No newline at end of file
+COPY ./requirements.txt /code/requirements.txt
+RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
+
+COPY ./app /code/app
+
+WORKDIR /code/app
+
+ENV HOST 0.0.0.0
+ENV PORT 80
+
+CMD ["sh", "-c", "uvicorn main:app --host $HOST --port $PORT"]
diff --git a/slo-checker/record-lag/requirements.txt b/slo-checker/record-lag/requirements.txt
index 8b6c3863226c2bd5e8bcd7982b2674dee593f192..770498e91e3f705e98868d009518b355a19a356a 100644
--- a/slo-checker/record-lag/requirements.txt
+++ b/slo-checker/record-lag/requirements.txt
@@ -1,5 +1,6 @@
-fastapi==0.65.2
-scikit-learn==0.20.3
-pandas==1.0.3
-uvicorn
 requests
+fastapi>=0.68.0,<0.69.0
+uvicorn>=0.15.0,<0.16.0
+#pydantic>=1.8.0,<2.0.0
+scikit-learn==0.22.2
+pandas==1.0.3
diff --git a/theodolite-benchmarks/beam-commons/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/beam-commons/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..60b9977149c7b281cb2ac91ee282f73d4351e348
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,127 @@
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=true
+cleanup.always_use_this_for_non_static_method_access=true
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=true
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_modifiers=false
+cleanup.remove_redundant_semicolons=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+cleanup_profile=_CAU-SE-Style
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_CAU-SE-Style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=true
+sp_cleanup.always_use_this_for_non_static_method_access=true
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.correct_indentation=true
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false
\ No newline at end of file
diff --git a/theodolite-benchmarks/beam-commons/build.gradle b/theodolite-benchmarks/beam-commons/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..a809f6bc4b97d8d62b807243eddecda8a5de5032
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/build.gradle
@@ -0,0 +1,33 @@
+plugins {
+    id 'theodolite.java-commons'
+}
+
+repositories {
+  mavenCentral()
+  maven {
+    url "https://oss.sonatype.org/content/repositories/snapshots/"
+  }
+  maven {
+    url 'https://packages.confluent.io/maven/'
+  }
+}
+
+dependencies {
+  // These dependencies are used internally, and not exposed to consumers on their own compile classpath.
+  implementation('org.industrial-devops:titan-ccp-common:0.1.0-SNAPSHOT') { changing = true }
+  implementation('org.industrial-devops:titan-ccp-common-kafka:0.1.0-SNAPSHOT') { changing = true }
+  implementation 'com.google.code.gson:gson:2.8.2'
+  implementation 'com.google.guava:guava:24.1-jre'
+
+  implementation('org.apache.beam:beam-sdks-java-io-kafka:2.22.0'){
+    exclude group: 'org.apache.kafka', module: 'kafka-clients'
+  }
+  implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30'
+  implementation group: 'org.apache.beam', name: 'beam-sdks-java-core', version: '2.22.0'
+
+  runtimeOnly 'org.slf4j:slf4j-api:1.7.32'
+  runtimeOnly 'org.slf4j:slf4j-jdk14:1.7.32'
+
+  // Use JUnit test framework
+  testImplementation 'junit:junit:4.12'
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractBeamService.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractBeamService.java
new file mode 100644
index 0000000000000000000000000000000000000000..03c5ca1daa7ffab71a4d08c04f677d7412e3a2be
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractBeamService.java
@@ -0,0 +1,45 @@
+package theodolite.commons.beam;
+
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.options.PipelineOptionsFactory;
+import org.apache.commons.configuration2.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import titan.ccp.common.configuration.ServiceConfigurations;
+
+/**
+ * Abstraction of a Beam microservice.
+ * Encapsulates the corresponding {@link PipelineOptions} and the beam Runner.
+ */
+public class AbstractBeamService {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractBeamService.class);
+
+  // Beam Pipeline
+  protected PipelineOptions options;
+
+  // Application Configurations
+  private final Configuration config = ServiceConfigurations.createWithDefaults();
+  private final String applicationName =
+      config.getString(ConfigurationKeys.APPLICATION_NAME);
+
+
+  /**
+   * Creates AbstractBeamService with options.
+   */
+  public AbstractBeamService(final String[] args) { //NOPMD
+    super();
+    LOGGER.info("Pipeline options:");
+    for (final String s : args) {
+      LOGGER.info("{}", s);
+    }
+    options = PipelineOptionsFactory.fromArgs(args).create();
+    options.setJobName(applicationName);
+    LOGGER.info("Starting BeamService with PipelineOptions {}:", this.options.toString());
+  }
+
+  public Configuration getConfig() {
+    return config;
+  }
+
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractPipeline.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractPipeline.java
new file mode 100644
index 0000000000000000000000000000000000000000..c936ce918c10f3c500cdd26f7e057cd7b6c555b6
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/AbstractPipeline.java
@@ -0,0 +1,50 @@
+package theodolite.commons.beam;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.beam.sdk.Pipeline;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+
+/**
+ * Abstraction of a Beam {@link Pipeline}.
+ */
+public class AbstractPipeline extends Pipeline {
+
+  protected final String inputTopic;
+  protected final String bootstrapServer;
+  // Application Configurations
+  private final Configuration config;
+
+  protected AbstractPipeline(final PipelineOptions options, final Configuration config) {
+    super(options);
+    this.config = config;
+
+    inputTopic = config.getString(ConfigurationKeys.KAFKA_INPUT_TOPIC);
+    bootstrapServer = config.getString(ConfigurationKeys.KAFKA_BOOTSTRAP_SERVERS);
+  }
+
+  /**
+   * Builds a simple configuration for a Kafka consumer transformation.
+   *
+   * @return the build configuration.
+   */
+  public Map<String, Object> buildConsumerConfig() {
+    final Map<String, Object> consumerConfig = new HashMap<>();
+    consumerConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,
+        config.getString(ConfigurationKeys.ENABLE_AUTO_COMMIT_CONFIG));
+    consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,
+        config
+            .getString(ConfigurationKeys.AUTO_OFFSET_RESET_CONFIG));
+    consumerConfig.put("schema.registry.url",
+        config.getString(ConfigurationKeys.SCHEMA_REGISTRY_URL));
+
+    consumerConfig.put("specific.avro.reader",
+        config.getString(ConfigurationKeys.SPECIFIC_AVRO_READER));
+
+    final String applicationName = config.getString(ConfigurationKeys.APPLICATION_NAME);
+    consumerConfig.put(ConsumerConfig.GROUP_ID_CONFIG, applicationName);
+    return consumerConfig;
+  }
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/ConfigurationKeys.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/ConfigurationKeys.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e4dc593c627282f5c6735a4d91e963d83af6865
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/ConfigurationKeys.java
@@ -0,0 +1,48 @@
+package theodolite.commons.beam;
+
+/**
+ * Keys to access configuration parameters.
+ */
+public final class ConfigurationKeys {
+  // Common keys
+  public static final String APPLICATION_NAME = "application.name";
+
+  public static final String KAFKA_BOOTSTRAP_SERVERS = "kafka.bootstrap.servers";
+
+  public static final String SCHEMA_REGISTRY_URL = "schema.registry.url";
+
+  public static final String KAFKA_INPUT_TOPIC = "kafka.input.topic";
+
+  // Additional topics
+  public static final String KAFKA_FEEDBACK_TOPIC = "kafka.feedback.topic";
+
+  public static final String KAFKA_OUTPUT_TOPIC = "kafka.output.topic";
+
+  public static final String KAFKA_CONFIGURATION_TOPIC = "kafka.configuration.topic";
+
+  // UC2
+  public static final String KAFKA_WINDOW_DURATION_MINUTES = "kafka.window.duration.minutes";
+
+  // UC3
+  public static final String AGGREGATION_DURATION_DAYS = "aggregation.duration.days";
+
+  public static final String AGGREGATION_ADVANCE_DAYS = "aggregation.advance.days";
+
+  // UC4
+  public static final String GRACE_PERIOD_MS = "grace.period.ms";
+
+
+  // BEAM
+  public static final String ENABLE_AUTO_COMMIT_CONFIG = "enable.auto.commit.config";
+
+  public static final String AUTO_OFFSET_RESET_CONFIG = "auto.offset.reset.config";
+
+  public static final String SPECIFIC_AVRO_READER = "specific.avro.reader";
+
+  public static final String TRIGGER_INTERVAL  = "trigger.interval";
+
+
+  private ConfigurationKeys() {
+  }
+
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/ActivePowerRecordDeserializer.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/ActivePowerRecordDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ec0f464d60e3e1166ec32145cf80e92a9aba241
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/ActivePowerRecordDeserializer.java
@@ -0,0 +1,6 @@
+package theodolite.commons.beam.kafka;
+
+import titan.ccp.model.records.ActivePowerRecord;
+
+public class ActivePowerRecordDeserializer extends TypedKafkaAvroDeserializer<ActivePowerRecord> {
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/EventTimePolicy.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/EventTimePolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..e568968670b3ea51388f3e2a19da8f64bd7c5391
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/EventTimePolicy.java
@@ -0,0 +1,35 @@
+package theodolite.commons.beam.kafka;
+
+import java.util.Optional;
+import org.apache.beam.sdk.io.kafka.KafkaRecord;
+import org.apache.beam.sdk.io.kafka.TimestampPolicy;
+import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
+import org.joda.time.Instant;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * TimeStampPolicy to use event time based on the timestamp of the record value.
+ */
+public class EventTimePolicy
+    extends TimestampPolicy<String, ActivePowerRecord> {
+  protected Instant currentWatermark;
+
+  public EventTimePolicy(final Optional<Instant> previousWatermark) {
+    super();
+    this.currentWatermark = previousWatermark.orElse(BoundedWindow.TIMESTAMP_MIN_VALUE);
+  }
+
+
+  @Override
+  public Instant getTimestampForRecord(final PartitionContext ctx,
+      final KafkaRecord<String, ActivePowerRecord> record) {
+    this.currentWatermark = new Instant(record.getKV().getValue().getTimestamp());
+    return this.currentWatermark;
+  }
+
+  @Override
+  public Instant getWatermark(final PartitionContext ctx) {
+    return this.currentWatermark;
+  }
+
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaActivePowerTimestampReader.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaActivePowerTimestampReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a48bd71d497f65351888425d092decf5adb05f3
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaActivePowerTimestampReader.java
@@ -0,0 +1,52 @@
+package theodolite.commons.beam.kafka;
+
+import java.util.Map;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.io.kafka.KafkaIO;
+import org.apache.beam.sdk.transforms.PTransform;
+import org.apache.beam.sdk.values.KV;
+import org.apache.beam.sdk.values.PBegin;
+import org.apache.beam.sdk.values.PCollection;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Simple {@link PTransform} that reads from Kafka using {@link KafkaIO} with event time.
+ */
+public class KafkaActivePowerTimestampReader
+    extends PTransform<PBegin, PCollection<KV<String, ActivePowerRecord>>> {
+
+  private static final long serialVersionUID = 2603286150183186115L;
+  private final PTransform<PBegin, PCollection<KV<String, ActivePowerRecord>>> reader;
+
+  /**
+   * Instantiates a {@link PTransform} that reads from Kafka with the given Configuration.
+   */
+  public KafkaActivePowerTimestampReader(
+      final String bootstrapServer,
+      final String inputTopic,
+      final Map<String, Object> consumerConfig) {
+    super();
+
+    // Check if bootstrap server and inputTopic are defined
+    if (bootstrapServer.isEmpty() || inputTopic.isEmpty()) {
+      throw new IllegalArgumentException("bootstrapServer or inputTopic missing");
+    }
+
+    this.reader = KafkaIO.<String, ActivePowerRecord>read().withBootstrapServers(bootstrapServer)
+        .withTopic(inputTopic).withKeyDeserializer(StringDeserializer.class)
+        .withValueDeserializerAndCoder(
+            ActivePowerRecordDeserializer.class,
+            AvroCoder.of(ActivePowerRecord.class))
+        .withConsumerConfigUpdates(consumerConfig)
+        .withTimestampPolicyFactory(
+            (tp, previousWatermark) -> new EventTimePolicy(previousWatermark))
+        .withoutMetadata();
+  }
+
+  @Override
+  public PCollection<KV<String, ActivePowerRecord>> expand(final PBegin input) {
+    return input.apply(this.reader);
+  }
+
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaGenericReader.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaGenericReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..83336b5a4c2451ef4bffefbd60ad9d52fccd9c17
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaGenericReader.java
@@ -0,0 +1,55 @@
+package theodolite.commons.beam.kafka;
+
+import java.util.Map;
+import org.apache.beam.sdk.io.kafka.KafkaIO;
+import org.apache.beam.sdk.transforms.PTransform;
+import org.apache.beam.sdk.values.KV;
+import org.apache.beam.sdk.values.PBegin;
+import org.apache.beam.sdk.values.PCollection;
+
+/**
+ * Simple {@link PTransform} that read from Kafka using {@link KafkaIO}.
+ *
+ * @param <K> Type of the Key.
+ * @param <V> Type of the Value.
+ */
+public class KafkaGenericReader<K, V> extends
+    PTransform<PBegin, PCollection<KV<K, V>>> {
+
+  private static final long serialVersionUID = 2603286150183186115L;
+  private final PTransform<PBegin, PCollection<KV<K, V>>> reader;
+
+  /**
+   * Instantiates a {@link PTransform} that reads from Kafka with the given Configuration.
+   */
+  public KafkaGenericReader(final String bootstrapServer, final String inputTopic,
+                            final Map<String, Object> consumerConfig,
+                            final Class<? extends
+                                org.apache.kafka.common.serialization.Deserializer<K>>
+                                  keyDeserializer,
+                            final Class<? extends
+                                org.apache.kafka.common.serialization.Deserializer<V>>
+                                  valueDeserializer) {
+    super();
+
+    // Check if boostrap server and inputTopic are defined
+    if (bootstrapServer.isEmpty() || inputTopic.isEmpty()) {
+      throw new IllegalArgumentException("bootstrapServer or inputTopic missing");
+    }
+
+    reader =
+        KafkaIO.<K, V>read()
+            .withBootstrapServers(bootstrapServer)
+            .withTopic(inputTopic)
+            .withKeyDeserializer(keyDeserializer)
+            .withValueDeserializer(valueDeserializer)
+            .withConsumerConfigUpdates(consumerConfig)
+            .withoutMetadata();
+  }
+
+  @Override
+  public PCollection<KV<K, V>> expand(final PBegin input) {
+    return input.apply(this.reader);
+  }
+
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaWriterTransformation.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaWriterTransformation.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a3867e71479e36ce30a9f222dfd0a7d473bd209
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/KafkaWriterTransformation.java
@@ -0,0 +1,45 @@
+package theodolite.commons.beam.kafka;
+
+import org.apache.beam.sdk.io.kafka.KafkaIO;
+import org.apache.beam.sdk.transforms.PTransform;
+import org.apache.beam.sdk.values.KV;
+import org.apache.beam.sdk.values.PCollection;
+import org.apache.beam.sdk.values.PDone;
+import org.apache.kafka.common.serialization.Serializer;
+import org.apache.kafka.common.serialization.StringSerializer;
+
+/**
+ * Wrapper for a Kafka writing Transformation
+ * where the value type can be generic.
+ * @param <T> type of the value.
+ */
+public class KafkaWriterTransformation<T> extends
+    PTransform<PCollection<KV<String, T>>, PDone> {
+
+  private static final long serialVersionUID = 3171423303843174723L;
+  private final PTransform<PCollection<KV<String, T>>, PDone> writer;
+
+  /**
+   * Creates a new kafka writer transformation.
+   */
+  public KafkaWriterTransformation(final String bootstrapServer, final String outputTopic,
+                                   final Class<? extends Serializer<T>> valueSerializer) {
+    super();
+    // Check if boostrap server and outputTopic are defined
+    if (bootstrapServer.isEmpty() || outputTopic.isEmpty()) {
+      throw new IllegalArgumentException("bootstrapServer or outputTopic missing");
+    }
+
+    this.writer = KafkaIO.<String, T>write()
+        .withBootstrapServers(bootstrapServer)
+        .withTopic(outputTopic)
+        .withKeySerializer(StringSerializer.class)
+        .withValueSerializer(valueSerializer);
+
+  }
+
+  @Override
+  public PDone expand(final PCollection<KV<String, T>> input) {
+    return input.apply(this.writer);
+  }
+}
diff --git a/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/TypedKafkaAvroDeserializer.java b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/TypedKafkaAvroDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..b213aa0bc8773604eb05e11c0546afcca3c137ac
--- /dev/null
+++ b/theodolite-benchmarks/beam-commons/src/main/java/theodolite/commons/beam/kafka/TypedKafkaAvroDeserializer.java
@@ -0,0 +1,49 @@
+package theodolite.commons.beam.kafka;
+
+import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient;
+import io.confluent.kafka.serializers.KafkaAvroDeserializer;
+import java.util.Map;
+import org.apache.avro.Schema;
+import org.apache.kafka.common.serialization.Deserializer;
+
+public class TypedKafkaAvroDeserializer<T> implements Deserializer<T> {
+
+	private final KafkaAvroDeserializer deserializer;
+
+	public TypedKafkaAvroDeserializer() {
+		this.deserializer = new KafkaAvroDeserializer();
+	}
+
+	public TypedKafkaAvroDeserializer(SchemaRegistryClient client) {
+		this.deserializer = new KafkaAvroDeserializer(client);
+	}
+
+	public TypedKafkaAvroDeserializer(SchemaRegistryClient client, Map<String, ?> props) {
+		this.deserializer = new KafkaAvroDeserializer(client, props);
+	}
+
+	@Override
+	public void configure(Map<String, ?> configs, boolean isKey) {
+
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public T deserialize(String s, byte[] bytes) {
+		return (T) this.deserializer.deserialize(s, bytes);
+	}
+
+	/**
+	 * Pass a reader schema to get an Avro projection
+	 */
+	@SuppressWarnings("unchecked")
+	public T deserialize(String topic, byte[] bytes, Schema readerSchema) {
+		return (T) this.deserializer.deserialize(topic, bytes, readerSchema);
+	}
+
+	@Override
+	public void close() {
+		this.deserializer.close();
+	}
+
+}
diff --git a/theodolite-benchmarks/buildSrc/build.gradle b/theodolite-benchmarks/buildSrc/build.gradle
index 4c099de32dc97ed3aa0417e8fff1f06e2a50dfd8..e3c3a8cb496929a5005c28d5d87b00bae85ad2cd 100644
--- a/theodolite-benchmarks/buildSrc/build.gradle
+++ b/theodolite-benchmarks/buildSrc/build.gradle
@@ -1,14 +1,3 @@
-buildscript {
-  repositories {
-    maven {
-      url "https://plugins.gradle.org/m2/"
-    }
-  }
-  dependencies {
-    classpath "com.github.jengelman.gradle.plugins:shadow:6.0.0"
-  }
-}
-
 // to discover the precompiled script plugins
 plugins {
     id 'groovy-gradle-plugin'
@@ -19,6 +8,6 @@ repositories {
 }
 
 dependencies {
-    implementation 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.6.0'
-    implementation 'com.github.jengelman.gradle.plugins:shadow:6.0.0'
+    implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.4'
+    implementation 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
 }
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.flink.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.flink.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..eb9bda1f84c4f20568fca1498462dff9082ea1fa
--- /dev/null
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.flink.gradle
@@ -0,0 +1,8 @@
+plugins {
+    id 'theodolite.beam'
+}
+
+dependencies {
+    implementation group: 'org.apache.beam', name: 'beam-runners-flink-1.12', version: '2.27.0'
+    implementation group: 'org.apache.flink', name: 'flink-statebackend-rocksdb_2.11', version: '1.12.0'
+}
\ No newline at end of file
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..41d1ae4f2bdfa358aca3fca2b91ea2b57e4c3405
--- /dev/null
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.gradle
@@ -0,0 +1,45 @@
+plugins {
+    // common java conventions
+    id 'theodolite.java-conventions'
+
+    // make executable
+    id 'application'
+}
+
+tasks.distZip.enabled = false
+
+repositories {
+    mavenCentral()
+    maven {
+        url "https://oss.sonatype.org/content/repositories/snapshots/"
+    }
+    maven {
+        url 'https://packages.confluent.io/maven/'
+    }
+}
+
+def apacheBeamVersion =  '2.22.0' //'2.27.0' // '2.34.0'
+
+dependencies {
+    // These dependencies are used internally, and not exposed to consumers on their own compile classpath.
+    implementation('org.industrial-devops:titan-ccp-common:0.1.0-SNAPSHOT') { changing = true }
+    implementation('org.industrial-devops:titan-ccp-common-kafka:0.1.0-SNAPSHOT') { changing = true }
+    implementation 'com.google.code.gson:gson:2.8.2'
+    implementation 'com.google.guava:guava:24.1-jre'
+    implementation 'org.slf4j:slf4j-simple:1.7.25'
+    implementation project(':beam-commons')
+
+    implementation group: 'org.apache.beam', name: 'beam-sdks-java-core', version: "${apacheBeamVersion}"
+
+    implementation("org.apache.beam:beam-sdks-java-io-kafka:${apacheBeamVersion}"){
+        exclude group: 'org.apache.kafka', module: 'kafka-clients'
+    }
+    implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30'
+
+    runtimeOnly "org.apache.beam:beam-runners-direct-java:${apacheBeamVersion}"
+    runtimeOnly 'org.slf4j:slf4j-api:1.7.32'
+    runtimeOnly 'org.slf4j:slf4j-jdk14:1.7.32'
+
+    // Use JUnit test framework
+    testImplementation 'junit:junit:4.12'
+}
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.samza.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.samza.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..73e916ccc867b9b3316776192f0dab56fa0710f0
--- /dev/null
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.beam.samza.gradle
@@ -0,0 +1,9 @@
+plugins {
+    id 'theodolite.beam'
+}
+
+dependencies {
+    implementation('org.apache.beam:beam-runners-samza:2.22.0') {
+        exclude group: 'org.apache.samza', module: 'samza-yarn_2.11'
+    }
+}
\ No newline at end of file
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.flink.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.flink.gradle
index 26a827b6049d09e422d48609590614f383f6cae8..f5e93dd88d2234f8a9b0d6fea880f47d652dccfa 100644
--- a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.flink.gradle
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.flink.gradle
@@ -25,7 +25,7 @@ ext {
 }
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.java-conventions.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.java-conventions.gradle
index 5b0e2a8a1211653428b296b11b14c1531e40e46b..a2912d96262f3d9c9f843ed508370e2798aeb735 100644
--- a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.java-conventions.gradle
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.java-conventions.gradle
@@ -66,5 +66,5 @@ spotbugs {
   reportLevel = "low"
   effort = "max"
   ignoreFailures = false
-  toolVersion = '4.1.4'
+  toolVersion = '4.5.3'
 }
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.kstreams.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.kstreams.gradle
index eece7b835ae9d6f39283ea371ce8b0b8194cdaa0..da2d42176ac0ddc9a157f843e3268b37ac4397e2 100644
--- a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.kstreams.gradle
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.kstreams.gradle
@@ -9,7 +9,7 @@ plugins {
 tasks.distZip.enabled = false
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
diff --git a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.load-generator.gradle b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.load-generator.gradle
index c6c2b6057cf35c32faa4d67b6ea6dba9e5c13beb..fb4fd89d1fe8a6d625a3ba7b459e9b0961befdbc 100644
--- a/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.load-generator.gradle
+++ b/theodolite-benchmarks/buildSrc/src/main/groovy/theodolite.load-generator.gradle
@@ -9,7 +9,7 @@ plugins {
 tasks.distZip.enabled = false
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
diff --git a/theodolite-benchmarks/docker-test/uc1-beam-flink/docker-compose.yml b/theodolite-benchmarks/docker-test/uc1-beam-flink/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1b683b4ca65a2582aa6a4d68444c4bbef7895b73
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc1-beam-flink/docker-compose.yml
@@ -0,0 +1,74 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
+  benchmark-jobmanager:
+      image: ghcr.io/cau-se/theodolite-uc1-beam-flink:${THEODOLITE_TAG:-latest}
+      #ports:
+      #  - "8080:8081"
+      command: >
+        standalone-job --job-classname application.Uc1BeamFlink
+        --disableMetrics=true 
+        --fasterCopy 
+      environment:
+        - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
+        - SCHEMA_REGISTRY_URL=http://schema-registry:8081
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          parallelism.default: 1
+      depends_on:
+        - schema-registry
+        - kafka
+  benchmark-taskmanager:
+      image: ghcr.io/cau-se/theodolite-uc1-beam-flink:${THEODOLITE_TAG:-latest}
+      scale: 1
+      command: taskmanager
+      environment:
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+      depends_on:
+        - schema-registry
+        - kafka
diff --git a/theodolite-benchmarks/docker-test/uc1-beam-samza/docker-compose.yml b/theodolite-benchmarks/docker-test/uc1-beam-samza/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5213799daa2d51eea53e794becdffc151a4da56
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc1-beam-samza/docker-compose.yml
@@ -0,0 +1,59 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    #ports:
+    #  - 2181:2181
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  benchmark:
+    image: ghcr.io/cau-se/theodolite-uc1-beam-samza:${THEODOLITE_TAG:-latest}
+    scale: 1
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      SAMZA_JOB_COORDINATOR_ZK_CONNECT: zookeeper:2181
+      SAMZA_SYSTEMS_KAFKA_PRODUCER_BOOTSTRAP_SERVERS: kafka:9092
+      SAMZA_SYSTEMS_KAFKA_CONSUMER_BOOTSTRAP_SERVERS: kafka:9092
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
diff --git a/theodolite-benchmarks/docker-test/uc1-flink-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc1-flink-docker-compose/docker-compose.yml
index 5a252f07e23205cf20390230ec956240ad2dc7a6..6c661bb49cb4173357acc89c06783fe5e0a2ce49 100755
--- a/theodolite-benchmarks/docker-test/uc1-flink-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc1-flink-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -44,7 +44,7 @@ services:
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
       NUM_SENSORS: 10
   benchmark-jobmanager:
-    image: ghcr.io/cau-se/theodolite-uc1-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc1-flink:${THEODOLITE_TAG:-latest}
     #ports:
     #  - "8080:8081"
     command: standalone-job --job-classname theodolite.uc1.application.HistoryServiceFlinkJob
@@ -59,7 +59,7 @@ services:
       - schema-registry
       - kafka
   benchmark-taskmanager:
-    image: ghcr.io/cau-se/theodolite-uc1-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc1-flink:${THEODOLITE_TAG:-latest}
     command: taskmanager
     environment:
       - |
diff --git a/theodolite-benchmarks/docker-test/uc1-kstreams-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc1-kstreams-docker-compose/docker-compose.yml
index 88ffadfcf3ce7e372fad1e3cbf28cc3aa847756d..25c19e35e5dae1807ef46fb8ade4e888dff0c2d8 100755
--- a/theodolite-benchmarks/docker-test/uc1-kstreams-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc1-kstreams-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   benchmark:
-    image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest
+    image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -41,7 +41,7 @@ services:
       KAFKA_BOOTSTRAP_SERVERS: kafka:9092
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc1-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
diff --git a/theodolite-benchmarks/docker-test/uc2-beam-flink/docker-compose.yml b/theodolite-benchmarks/docker-test/uc2-beam-flink/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8427161e43faa920e011973d76f32f9cc9d62f8c
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc2-beam-flink/docker-compose.yml
@@ -0,0 +1,74 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
+  benchmark-jobmanager:
+      image: ghcr.io/cau-se/theodolite-uc2-beam-flink:${THEODOLITE_TAG:-latest}
+      #ports:
+      #  - "8080:8081"
+      command: >
+        standalone-job --job-classname application.Uc2BeamFlink
+        --disableMetrics=true 
+        --fasterCopy 
+      environment:
+        - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
+        - SCHEMA_REGISTRY_URL=http://schema-registry:8081
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          parallelism.default: 1
+      depends_on:
+        - schema-registry
+        - kafka
+  benchmark-taskmanager:
+      image: ghcr.io/cau-se/theodolite-uc2-beam-flink:${THEODOLITE_TAG:-latest}
+      scale: 1
+      command: taskmanager
+      environment:
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+      depends_on:
+        - schema-registry
+        - kafka
diff --git a/theodolite-benchmarks/docker-test/uc2-beam-samza/docker-compose.yml b/theodolite-benchmarks/docker-test/uc2-beam-samza/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..820bb25dd370cf6c7410b20fbdbdb1d4281f47d3
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc2-beam-samza/docker-compose.yml
@@ -0,0 +1,59 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    #ports:
+    #  - 2181:2181
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  benchmark:
+    image: ghcr.io/cau-se/theodolite-uc2-beam-samza:${THEODOLITE_TAG:-latest}
+    scale: 1
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      SAMZA_JOB_COORDINATOR_ZK_CONNECT: zookeeper:2181
+      SAMZA_SYSTEMS_KAFKA_PRODUCER_BOOTSTRAP_SERVERS: kafka:9092
+      SAMZA_SYSTEMS_KAFKA_CONSUMER_BOOTSTRAP_SERVERS: kafka:9092
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
diff --git a/theodolite-benchmarks/docker-test/uc2-flink-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc2-flink-docker-compose/docker-compose.yml
index f7047a7af7d0b613dd128b9d8d4d9fffd22b4692..e51c544bfd64c6f540f5a84a5f617de745a1a6b6 100755
--- a/theodolite-benchmarks/docker-test/uc2-flink-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc2-flink-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -44,7 +44,7 @@ services:
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
       NUM_SENSORS: 10  
   benchmark-jobmanager:
-    image: ghcr.io/cau-se/theodolite-uc2-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc2-flink:${THEODOLITE_TAG:-latest}
     #ports:
     #  - "8080:8081"
     command: standalone-job --job-classname theodolite.uc2.application.HistoryServiceFlinkJob
@@ -59,7 +59,7 @@ services:
       - schema-registry
       - kafka
   benchmark-taskmanager:
-    image: ghcr.io/cau-se/theodolite-uc2-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc2-flink:${THEODOLITE_TAG:-latest}
     command: taskmanager
     environment:
       - |
diff --git a/theodolite-benchmarks/docker-test/uc2-kstreams-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc2-kstreams-docker-compose/docker-compose.yml
index 89f2633b390b08a3a18128e98f261cc264e2b41d..4cc4978d844a8b361af4061ab41d1adc4e7a1813 100755
--- a/theodolite-benchmarks/docker-test/uc2-kstreams-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc2-kstreams-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   benchmark:
-    image: ghcr.io/cau-se/theodolite-uc2-kstreams-app:latest
+    image: ghcr.io/cau-se/theodolite-uc2-kstreams-app:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -42,7 +42,7 @@ services:
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
       KAFKA_WINDOW_DURATION_MINUTES: 60
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc2-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
diff --git a/theodolite-benchmarks/docker-test/uc3-beam-flink/docker-compose.yml b/theodolite-benchmarks/docker-test/uc3-beam-flink/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b9e934b61f9742ad4e83601ff13707be0641c875
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc3-beam-flink/docker-compose.yml
@@ -0,0 +1,78 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
+  benchmark-jobmanager:
+      image: ghcr.io/cau-se/theodolite-uc3-beam-flink:${THEODOLITE_TAG:-latest}
+      #ports:
+      #  - "8080:8081"
+      command: >
+        standalone-job --job-classname application.Uc3BeamFlink
+        --disableMetrics=true 
+        --fasterCopy 
+      environment:
+        - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
+        - SCHEMA_REGISTRY_URL=http://schema-registry:8081
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          parallelism.default: 1
+          state.backend: rocksdb
+          state.checkpoints.dir: file:///data/flink/checkpoints
+      depends_on:
+        - schema-registry
+        - kafka
+  benchmark-taskmanager:
+      image: ghcr.io/cau-se/theodolite-uc3-beam-flink:${THEODOLITE_TAG:-latest}
+      scale: 1
+      command: taskmanager
+      environment:
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          state.backend: rocksdb
+          state.checkpoints.dir: file:///data/flink/checkpoints
+      depends_on:
+        - schema-registry
+        - kafka
diff --git a/theodolite-benchmarks/docker-test/uc3-beam-samza/docker-compose.yml b/theodolite-benchmarks/docker-test/uc3-beam-samza/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3ae3a2faa7c0849b76ea838b09ba0254bdfea936
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc3-beam-samza/docker-compose.yml
@@ -0,0 +1,59 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    #ports:
+    #  - 2181:2181
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  benchmark:
+    image: ghcr.io/cau-se/theodolite-uc3-beam-samza:${THEODOLITE_TAG:-latest}
+    scale: 1
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      SAMZA_JOB_COORDINATOR_ZK_CONNECT: zookeeper:2181
+      SAMZA_SYSTEMS_KAFKA_PRODUCER_BOOTSTRAP_SERVERS: kafka:9092
+      SAMZA_SYSTEMS_KAFKA_CONSUMER_BOOTSTRAP_SERVERS: kafka:9092
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 10
diff --git a/theodolite-benchmarks/docker-test/uc3-flink-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc3-flink-docker-compose/docker-compose.yml
index c2b8d7ad436301138acdf8dfae1654e2feb9b9bb..42c55950fbea022f00019699ae72678fbe88b8cb 100755
--- a/theodolite-benchmarks/docker-test/uc3-flink-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc3-flink-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -44,7 +44,7 @@ services:
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
       NUM_SENSORS: 10
   benchmark-jobmanager:
-    image: ghcr.io/cau-se/theodolite-uc3-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc3-flink:${THEODOLITE_TAG:-latest}
     #ports:
     #  - "8080:8081"
     command: standalone-job --job-classname theodolite.uc3.application.HistoryServiceFlinkJob
@@ -59,7 +59,7 @@ services:
       - schema-registry
       - kafka
   benchmark-taskmanager:
-    image: ghcr.io/cau-se/theodolite-uc3-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc3-flink:${THEODOLITE_TAG:-latest}
     command: taskmanager
     environment:
       - |
diff --git a/theodolite-benchmarks/docker-test/uc3-kstreams-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc3-kstreams-docker-compose/docker-compose.yml
index 65b0a3467e123a84d0e719d8702749ed33773aea..f943de372b75bf64df0028679d56950fedfaec48 100755
--- a/theodolite-benchmarks/docker-test/uc3-kstreams-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc3-kstreams-docker-compose/docker-compose.yml
@@ -19,7 +19,7 @@ services:
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
       KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
       KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
-      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1"
   schema-registry:
     image: confluentinc/cp-schema-registry:5.3.1
     depends_on:
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   benchmark:
-    image: ghcr.io/cau-se/theodolite-uc3-kstreams-app:latest
+    image: ghcr.io/cau-se/theodolite-uc3-kstreams-app:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -41,7 +41,7 @@ services:
       KAFKA_BOOTSTRAP_SERVERS: kafka:9092
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc3-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
diff --git a/theodolite-benchmarks/docker-test/uc4-beam-flink/docker-compose.yml b/theodolite-benchmarks/docker-test/uc4-beam-flink/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b0d18dfe34c23847b3cc0b04d788fdc4b3552e68
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc4-beam-flink/docker-compose.yml
@@ -0,0 +1,80 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 4
+      NUM_NESTED_GROUPS: 4
+  benchmark-jobmanager:
+      image: ghcr.io/cau-se/theodolite-uc4-beam-flink:${THEODOLITE_TAG:-latest}
+      #ports:
+      #  - "8080:8081"
+      command: >
+        standalone-job
+        --job-classname application.Uc4BeamFlink
+        --disableMetrics=true 
+        --fasterCopy 
+      environment:
+        - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
+        - SCHEMA_REGISTRY_URL=http://schema-registry:8081
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          parallelism.default: 1
+          state.backend: rocksdb
+          state.checkpoints.dir: file:///data/flink/checkpoints
+      depends_on:
+        - schema-registry
+        - kafka
+  benchmark-taskmanager:
+      image: ghcr.io/cau-se/theodolite-uc4-beam-flink:${THEODOLITE_TAG:-latest}
+      scale: 1
+      command: taskmanager
+      environment:
+        - |
+          FLINK_PROPERTIES=
+          jobmanager.rpc.address: benchmark-jobmanager
+          state.backend: rocksdb
+          state.checkpoints.dir: file:///data/flink/checkpoints
+      depends_on:
+        - schema-registry
+        - kafka
diff --git a/theodolite-benchmarks/docker-test/uc4-beam-samza/docker-compose.yml b/theodolite-benchmarks/docker-test/uc4-beam-samza/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b9e4142cb0be2701f7772cff5725b524aaffcbc7
--- /dev/null
+++ b/theodolite-benchmarks/docker-test/uc4-beam-samza/docker-compose.yml
@@ -0,0 +1,60 @@
+version: '2.2'
+services:
+  zookeeper:
+    image: confluentinc/cp-zookeeper
+    expose:
+      - "2181"
+    #ports:
+    #  - 2181:2181
+    environment:
+      ZOOKEEPER_CLIENT_PORT: 2181
+  kafka:
+    image: wurstmeister/kafka
+    expose:
+      - "9092"
+    #ports:
+    #  - 19092:19092
+    environment:
+      KAFKA_LISTENERS: PLAINTEXT://:9092,CONNECTIONS_FROM_HOST://:19092
+      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,CONNECTIONS_FROM_HOST://localhost:19092
+      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
+      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+      KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 30000
+      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
+      KAFKA_CREATE_TOPICS: "input:3:1,output:3:1,configuration:3:1,aggregation-feedback:3:1"
+  schema-registry:
+    image: confluentinc/cp-schema-registry:5.3.1
+    depends_on:
+      - zookeeper
+      - kafka
+    expose:
+      - "8081"
+    #ports:
+    #  - 8081:8081
+    environment:
+      SCHEMA_REGISTRY_HOST_NAME: schema-registry
+      SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
+  benchmark:
+    image: ghcr.io/cau-se/theodolite-uc4-beam-samza:${THEODOLITE_TAG:-latest}
+    scale: 1
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      SAMZA_JOB_COORDINATOR_ZK_CONNECT: zookeeper:2181
+      SAMZA_SYSTEMS_KAFKA_PRODUCER_BOOTSTRAP_SERVERS: kafka:9092
+      SAMZA_SYSTEMS_KAFKA_CONSUMER_BOOTSTRAP_SERVERS: kafka:9092
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+  load-generator: 
+    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:${THEODOLITE_TAG:-latest}
+    depends_on:
+      - schema-registry
+      - kafka
+    environment:
+      BOOTSTRAP_SERVER: load-generator:5701
+      PORT: 5701
+      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
+      SCHEMA_REGISTRY_URL: http://schema-registry:8081
+      NUM_SENSORS: 4
+      NUM_NESTED_GROUPS: 4
diff --git a/theodolite-benchmarks/docker-test/uc4-flink-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc4-flink-docker-compose/docker-compose.yml
index 0f7e4e656dede1aad3342fb79816e3ebf88e84d8..5a5a5924ef13cc9691b267ce6169bdb85a1e267b 100755
--- a/theodolite-benchmarks/docker-test/uc4-flink-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc4-flink-docker-compose/docker-compose.yml
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -45,7 +45,7 @@ services:
       NUM_SENSORS: 4
       NUM_NESTED_GROUPS: 4
   benchmark-jobmanager:
-    image: ghcr.io/cau-se/theodolite-uc4-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc4-flink:${THEODOLITE_TAG:-latest}
     #ports:
     #  - "8080:8081"
     command: standalone-job --job-classname theodolite.uc4.application.AggregationServiceFlinkJob
@@ -60,7 +60,7 @@ services:
       - schema-registry
       - kafka
   benchmark-taskmanager:
-    image: ghcr.io/cau-se/theodolite-uc4-flink:latest
+    image: ghcr.io/cau-se/theodolite-uc4-flink:${THEODOLITE_TAG:-latest}
     command: taskmanager
     environment:
       - |
diff --git a/theodolite-benchmarks/docker-test/uc4-kstreams-docker-compose/docker-compose.yml b/theodolite-benchmarks/docker-test/uc4-kstreams-docker-compose/docker-compose.yml
index 5fca44708006d1fae3ae2f9f46b5c42f6431fc3a..1818505787f662d57f1a96c8c79b03d61cf9da2a 100755
--- a/theodolite-benchmarks/docker-test/uc4-kstreams-docker-compose/docker-compose.yml
+++ b/theodolite-benchmarks/docker-test/uc4-kstreams-docker-compose/docker-compose.yml
@@ -33,7 +33,7 @@ services:
       SCHEMA_REGISTRY_HOST_NAME: schema-registry
       SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
   benchmark:
-    image: ghcr.io/cau-se/theodolite-uc4-kstreams-app:latest
+    image: ghcr.io/cau-se/theodolite-uc4-kstreams-app:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
@@ -41,7 +41,7 @@ services:
       KAFKA_BOOTSTRAP_SERVERS: kafka:9092
       SCHEMA_REGISTRY_URL: http://schema-registry:8081
   load-generator: 
-    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:latest
+    image: ghcr.io/cau-se/theodolite-uc4-workload-generator:${THEODOLITE_TAG:-latest}
     depends_on:
       - schema-registry
       - kafka
diff --git a/theodolite-benchmarks/flink-commons/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/flink-commons/.settings/org.eclipse.jdt.ui.prefs
index 66b402b58f39b79066638ce679c27c0378d5be54..174249a98f9d91ce2cbf2bb64b27c09b37f05d9f 100644
--- a/theodolite-benchmarks/flink-commons/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/flink-commons/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/flink-commons/build.gradle b/theodolite-benchmarks/flink-commons/build.gradle
index edd48c914b8c909ff196bb98e9bbc8b9d99865b9..a3a4a35752006bb10e15ff508ce0b37f70adc57d 100644
--- a/theodolite-benchmarks/flink-commons/build.gradle
+++ b/theodolite-benchmarks/flink-commons/build.gradle
@@ -8,7 +8,7 @@ ext {
 }
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
diff --git a/theodolite-benchmarks/gradle/wrapper/gradle-wrapper.properties b/theodolite-benchmarks/gradle/wrapper/gradle-wrapper.properties
index 442d9132ea32808ad980df4bd233b359f76341a7..2e6e5897b5285c749d75662c65ac5d2904c37bc6 100644
--- a/theodolite-benchmarks/gradle/wrapper/gradle-wrapper.properties
+++ b/theodolite-benchmarks/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/theodolite-benchmarks/kstreams-commons/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/kstreams-commons/.settings/org.eclipse.jdt.ui.prefs
index 98b5ca8064a352aacfe2aebd13fbd0a87735fc3e..713419c8d3d74d3bd7fd05c3e839367753fcdee0 100644
--- a/theodolite-benchmarks/kstreams-commons/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/kstreams-commons/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/kstreams-commons/build.gradle b/theodolite-benchmarks/kstreams-commons/build.gradle
index c5a880acd4377056cc0b0f06b33a2d74c9f87c4e..7683ffe39314ec375eda0ed4e139d618d44a7328 100644
--- a/theodolite-benchmarks/kstreams-commons/build.gradle
+++ b/theodolite-benchmarks/kstreams-commons/build.gradle
@@ -3,7 +3,7 @@ plugins {
 }
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
diff --git a/theodolite-benchmarks/load-generator-commons/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/load-generator-commons/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/load-generator-commons/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/load-generator-commons/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/load-generator-commons/build.gradle b/theodolite-benchmarks/load-generator-commons/build.gradle
index 118f3e648f829a3eafe719ddf660d35ac8563574..2d8f77b5154b5b788e0729da69122b443740ce75 100644
--- a/theodolite-benchmarks/load-generator-commons/build.gradle
+++ b/theodolite-benchmarks/load-generator-commons/build.gradle
@@ -3,7 +3,7 @@ plugins {
 }
 
 repositories {
-  jcenter()
+  mavenCentral()
   maven {
     url "https://oss.sonatype.org/content/repositories/snapshots/"
   }
@@ -13,14 +13,16 @@ repositories {
 }
 
 dependencies {
-  implementation 'com.google.guava:guava:30.1-jre'
   implementation 'com.hazelcast:hazelcast:4.1.1'
   implementation 'com.hazelcast:hazelcast-kubernetes:2.2.1'
   implementation 'org.slf4j:slf4j-simple:1.7.25'
+  implementation 'com.google.guava:guava:30.1-jre'
+  implementation 'com.google.code.gson:gson:2.8.2'
   implementation('org.industrial-devops:titan-ccp-common:0.1.0-SNAPSHOT') { changing = true }
   implementation('org.industrial-devops:titan-ccp-common-kafka:0.1.0-SNAPSHOT') { changing = true }
   implementation 'org.apache.kafka:kafka-streams:2.6.0' // TODO required?
 
   // Use JUnit test framework
   testImplementation 'junit:junit:4.12'
+  testImplementation 'com.github.tomakehurst:wiremock-jre8:2.32.0'
 }
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/ConfigurationKeys.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/ConfigurationKeys.java
index 45ac1d5bb9c21a1b6303de2f248d08b69c02fc28..7a60e271f04e396b2e0c69b1fcfee1d8a1ca8a7d 100644
--- a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/ConfigurationKeys.java
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/ConfigurationKeys.java
@@ -23,6 +23,8 @@ public final class ConfigurationKeys {
 
   public static final String THREADS = "THREADS";
 
+  public static final String TARGET = "TARGET";
+
   public static final String KAFKA_BOOTSTRAP_SERVERS = "KAFKA_BOOTSTRAP_SERVERS";
 
   public static final String SCHEMA_REGISTRY_URL = "SCHEMA_REGISTRY_URL";
@@ -35,6 +37,8 @@ public final class ConfigurationKeys {
 
   public static final String KAFKA_BUFFER_MEMORY = "KAFKA_BUFFER_MEMORY";
 
+  public static final String HTTP_URL = "HTTP_URL";
+
   private ConfigurationKeys() {}
 
 }
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/HttpRecordSender.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/HttpRecordSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b7a5db067c8117f046aa0ff1c6f5d56c35c4321
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/HttpRecordSender.java
@@ -0,0 +1,93 @@
+package theodolite.commons.workloadgeneration;
+
+import com.google.gson.Gson;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import org.apache.avro.specific.SpecificRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sends monitoring records via HTTP.
+ *
+ * @param <T> {@link SpecificRecord} to send
+ */
+public class HttpRecordSender<T extends SpecificRecord> implements RecordSender<T> {
+
+  private static final int HTTP_OK = 200;
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(HttpRecordSender.class);
+
+  private final Gson gson = new Gson();
+
+  private final HttpClient httpClient = HttpClient.newBuilder().build();
+
+  private final URI uri;
+
+  private final boolean async;
+
+  private final List<Integer> validStatusCodes;
+
+  /**
+   * Create a new {@link HttpRecordSender}.
+   *
+   * @param uri the {@link URI} records should be sent to
+   */
+  public HttpRecordSender(final URI uri) {
+    this(uri, true, List.of(HTTP_OK));
+  }
+
+  /**
+   * Create a new {@link HttpRecordSender}.
+   *
+   * @param uri the {@link URI} records should be sent to
+   * @param async whether HTTP requests should be sent asynchronous
+   * @param validStatusCodes a list of HTTP status codes which are considered as successful
+   */
+  public HttpRecordSender(final URI uri, final boolean async,
+      final List<Integer> validStatusCodes) {
+    this.uri = uri;
+    this.async = async;
+    this.validStatusCodes = validStatusCodes;
+  }
+
+  @Override
+  public void send(final T message) {
+    final String json = this.gson.toJson(message);
+    final HttpRequest request = HttpRequest.newBuilder()
+        .uri(this.uri)
+        .POST(HttpRequest.BodyPublishers.ofString(json))
+        .build();
+    final BodyHandler<Void> bodyHandler = BodyHandlers.discarding();
+    // final BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+
+    final CompletableFuture<HttpResponse<Void>> result =
+        this.httpClient.sendAsync(request, bodyHandler)
+            .whenComplete((response, exception) -> {
+              if (exception != null) { // NOPMD
+                LOGGER.warn("Couldn't send request to {}.", this.uri, exception); // NOPMD false-p.
+              } else if (!this.validStatusCodes.contains(response.statusCode())) { // NOPMD
+                LOGGER.warn("Received status code {} for request to {}.", response.statusCode(),
+                    this.uri);
+              } else {
+                LOGGER.debug("Sucessfully sent request to {} (status={}).", this.uri,
+                    response.statusCode());
+              }
+            });
+    if (this.async) {
+      try {
+        result.get();
+      } catch (InterruptedException | ExecutionException e) {
+        LOGGER.error("Couldn't get result for request to {}.", this.uri, e);
+      }
+    }
+  }
+
+}
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/KafkaRecordSender.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/KafkaRecordSender.java
index ded7c347c8d6b057581dc63b691df5bb60997791..44ff8a92afd5356b4bb2af203899a61f7af48b2d 100644
--- a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/KafkaRecordSender.java
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/KafkaRecordSender.java
@@ -15,7 +15,7 @@ import titan.ccp.common.kafka.avro.SchemaRegistryAvroSerdeFactory;
 /**
  * Sends monitoring records to Kafka.
  *
- * @param <T> {@link IMonitoringRecord} to send
+ * @param <T> {@link SpecificRecord} to send
  */
 public class KafkaRecordSender<T extends SpecificRecord> implements RecordSender<T> {
 
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGenerator.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGenerator.java
index 73f064d1ce44ff8a613f9ce0a7b9a64d4bac6c38..6453ef0bd3b6d5a3b5f7f2b77fa20da8f79cb35f 100644
--- a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGenerator.java
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGenerator.java
@@ -1,11 +1,13 @@
 package theodolite.commons.workloadgeneration;
 
+import java.net.URI;
 import java.time.Duration;
 import java.util.Objects;
 import java.util.Properties;
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import titan.ccp.model.records.ActivePowerRecord;
 
 /**
  * A Theodolite load generator.
@@ -20,9 +22,11 @@ public final class LoadGenerator {
   private static final int PERIOD_MS_DEFAULT = 1000;
   private static final int VALUE_DEFAULT = 10;
   private static final int THREADS_DEFAULT = 4;
+  private static final LoadGeneratorTarget TARGET_DEFAULT = LoadGeneratorTarget.KAFKA;
   private static final String SCHEMA_REGISTRY_URL_DEFAULT = "http://localhost:8081";
   private static final String KAFKA_TOPIC_DEFAULT = "input";
-  private static final String KAFKA_BOOTSTRAP_SERVERS_DEFAULT = "localhost:19092"; // NOPMD
+  private static final String KAFKA_BOOTSTRAP_SERVERS_DEFAULT = "localhost:9092"; // NOPMD
+  private static final String HTTP_URI_DEFAULT = "http://localhost:8080";
 
   private ClusterConfig clusterConfig;
   private WorkloadDefinition loadDefinition;
@@ -91,7 +95,7 @@ public final class LoadGenerator {
             new KeySpace(SENSOR_PREFIX_DEFAULT, NUMBER_OF_KEYS_DEFAULT),
             Duration.ofMillis(PERIOD_MS_DEFAULT)))
         .setGeneratorConfig(new LoadGeneratorConfig(
-            TitanRecordGeneratorFactory.forConstantValue(VALUE_DEFAULT),
+            TitanRecordGenerator.forConstantValue(VALUE_DEFAULT),
             TitanKafkaSenderFactory.forKafkaConfig(
                 KAFKA_BOOTSTRAP_SERVERS_DEFAULT,
                 KAFKA_TOPIC_DEFAULT,
@@ -134,6 +138,47 @@ public final class LoadGenerator {
       clusterConfig.setClusterNamePrefix(portAutoIncrement);
     }
 
+    final LoadGeneratorTarget target = LoadGeneratorTarget.from(
+        Objects.requireNonNullElse(System.getenv(ConfigurationKeys.TARGET),
+            TARGET_DEFAULT.getValue()));
+
+    final RecordSender<ActivePowerRecord> recordSender; // NOPMD
+    if (target == LoadGeneratorTarget.KAFKA) {
+      final String kafkaBootstrapServers = Objects.requireNonNullElse(
+          System.getenv(ConfigurationKeys.KAFKA_BOOTSTRAP_SERVERS),
+          KAFKA_BOOTSTRAP_SERVERS_DEFAULT);
+      final String kafkaInputTopic = Objects.requireNonNullElse(
+          System.getenv(ConfigurationKeys.KAFKA_INPUT_TOPIC),
+          KAFKA_TOPIC_DEFAULT);
+      final String schemaRegistryUrl = Objects.requireNonNullElse(
+          System.getenv(ConfigurationKeys.SCHEMA_REGISTRY_URL),
+          SCHEMA_REGISTRY_URL_DEFAULT);
+      final Properties kafkaProperties = new Properties();
+      kafkaProperties.compute(ProducerConfig.BATCH_SIZE_CONFIG,
+          (k, v) -> System.getenv(ConfigurationKeys.KAFKA_BATCH_SIZE));
+      kafkaProperties.compute(ProducerConfig.LINGER_MS_CONFIG,
+          (k, v) -> System.getenv(ConfigurationKeys.KAFKA_LINGER_MS));
+      kafkaProperties.compute(ProducerConfig.BUFFER_MEMORY_CONFIG,
+          (k, v) -> System.getenv(ConfigurationKeys.KAFKA_BUFFER_MEMORY));
+      recordSender = TitanKafkaSenderFactory.forKafkaConfig(
+          kafkaBootstrapServers,
+          kafkaInputTopic,
+          schemaRegistryUrl);
+      LOGGER.info(
+          "Use Kafka as target with bootstrap server '{}', schema registry url '{}' and topic '{}'.", // NOCS
+          kafkaBootstrapServers, schemaRegistryUrl, kafkaInputTopic);
+    } else if (target == LoadGeneratorTarget.HTTP) {
+      final URI url = URI.create(
+          Objects.requireNonNullElse(
+              System.getenv(ConfigurationKeys.HTTP_URL),
+              HTTP_URI_DEFAULT));
+      recordSender = new HttpRecordSender<>(url);
+      LOGGER.info("Use HTTP server as target with url '{}'.", url);
+    } else {
+      // Should never happen
+      throw new IllegalStateException("Target " + target + " is not handled yet.");
+    }
+
     final int numSensors = Integer.parseInt(Objects.requireNonNullElse(
         System.getenv(ConfigurationKeys.NUM_SENSORS),
         Integer.toString(NUMBER_OF_KEYS_DEFAULT)));
@@ -146,22 +191,6 @@ public final class LoadGenerator {
     final int threads = Integer.parseInt(Objects.requireNonNullElse(
         System.getenv(ConfigurationKeys.THREADS),
         Integer.toString(THREADS_DEFAULT)));
-    final String kafkaBootstrapServers = Objects.requireNonNullElse(
-        System.getenv(ConfigurationKeys.KAFKA_BOOTSTRAP_SERVERS),
-        KAFKA_BOOTSTRAP_SERVERS_DEFAULT);
-    final String kafkaInputTopic = Objects.requireNonNullElse(
-        System.getenv(ConfigurationKeys.KAFKA_INPUT_TOPIC),
-        KAFKA_TOPIC_DEFAULT);
-    final String schemaRegistryUrl = Objects.requireNonNullElse(
-        System.getenv(ConfigurationKeys.SCHEMA_REGISTRY_URL),
-        SCHEMA_REGISTRY_URL_DEFAULT);
-    final Properties kafkaProperties = new Properties();
-    kafkaProperties.compute(ProducerConfig.BATCH_SIZE_CONFIG,
-        (k, v) -> System.getenv(ConfigurationKeys.KAFKA_BATCH_SIZE));
-    kafkaProperties.compute(ProducerConfig.LINGER_MS_CONFIG,
-        (k, v) -> System.getenv(ConfigurationKeys.KAFKA_LINGER_MS));
-    kafkaProperties.compute(ProducerConfig.BUFFER_MEMORY_CONFIG,
-        (k, v) -> System.getenv(ConfigurationKeys.KAFKA_BUFFER_MEMORY));
 
     return new LoadGenerator()
         .setClusterConfig(clusterConfig)
@@ -169,11 +198,8 @@ public final class LoadGenerator {
             new KeySpace(SENSOR_PREFIX_DEFAULT, numSensors),
             Duration.ofMillis(periodMs)))
         .setGeneratorConfig(new LoadGeneratorConfig(
-            TitanRecordGeneratorFactory.forConstantValue(value),
-            TitanKafkaSenderFactory.forKafkaConfig(
-                kafkaBootstrapServers,
-                kafkaInputTopic,
-                schemaRegistryUrl)))
+            TitanRecordGenerator.forConstantValue(value),
+            recordSender))
         .withThreads(threads);
   }
 
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGeneratorTarget.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGeneratorTarget.java
new file mode 100644
index 0000000000000000000000000000000000000000..086e4de36301693c6873016122a47709b858a0d4
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/LoadGeneratorTarget.java
@@ -0,0 +1,26 @@
+package theodolite.commons.workloadgeneration;
+
+import java.util.stream.Stream;
+
+enum LoadGeneratorTarget {
+
+  KAFKA("kafka"), HTTP("http");
+
+  private final String value;
+
+  LoadGeneratorTarget(final String value) {
+    this.value = value;
+  }
+
+  String getValue() {
+    return this.value;
+  }
+
+  static LoadGeneratorTarget from(final String value) {
+    return Stream.of(LoadGeneratorTarget.values())
+        .filter(t -> t.value.equals(value))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException("Target '" + value + "' does not exist."));
+  }
+
+}
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGenerator.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..cebdacaee9a8e7d05787fdf3f846d49914574828
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGenerator.java
@@ -0,0 +1,38 @@
+package theodolite.commons.workloadgeneration;
+
+import java.time.Clock;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * A factory for creating {@link RecordGenerator}s that creates Titan {@link ActivePowerRecord}s.
+ */
+public final class TitanRecordGenerator implements RecordGenerator<ActivePowerRecord> {
+
+  private final Clock clock;
+
+  private final double constantValue;
+
+  private TitanRecordGenerator(final double constantValue) {
+    this.constantValue = constantValue;
+    this.clock = Clock.systemUTC();
+  }
+
+  /* default */ TitanRecordGenerator(final double constantValue, final Clock clock) {
+    this.constantValue = constantValue;
+    this.clock = clock;
+  }
+
+  /**
+   * Create a {@link RecordGenerator} that generates Titan {@link ActivePowerRecord}s with a
+   * constant value.
+   */
+  public static RecordGenerator<ActivePowerRecord> forConstantValue(final double value) {
+    return new TitanRecordGenerator(value);
+  }
+
+  @Override
+  public ActivePowerRecord generate(final String key) {
+    return new ActivePowerRecord(key, this.clock.millis(), this.constantValue);
+  }
+
+}
diff --git a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorFactory.java b/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorFactory.java
deleted file mode 100644
index 4e1c10071eff28d77514dbc121e30bead3f6fa74..0000000000000000000000000000000000000000
--- a/theodolite-benchmarks/load-generator-commons/src/main/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorFactory.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package theodolite.commons.workloadgeneration;
-
-import titan.ccp.model.records.ActivePowerRecord;
-
-/**
- * A factory for creating {@link RecordGenerator}s that creates Titan {@link ActivePowerRecord}s.
- */
-public final class TitanRecordGeneratorFactory {
-
-
-  private TitanRecordGeneratorFactory() {}
-
-  /**
-   * Create a {@link RecordGenerator} that generates Titan {@link ActivePowerRecord}s with a
-   * constant value.
-   */
-  public static RecordGenerator<ActivePowerRecord> forConstantValue(final double value) {
-    return sensor -> new ActivePowerRecord(sensor, System.currentTimeMillis(), value);
-  }
-
-}
diff --git a/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/HttpRecordSenderTest.java b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/HttpRecordSenderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d908d34b7c6b87254782b6ae8b0b8dc2a6d036e
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/HttpRecordSenderTest.java
@@ -0,0 +1,53 @@
+package theodolite.commons.workloadgeneration;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.exactly;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.google.gson.Gson;
+import java.net.URI;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import titan.ccp.model.records.ActivePowerRecord;
+
+public class HttpRecordSenderTest {
+
+  private HttpRecordSender<ActivePowerRecord> httpRecordSender;
+
+  private Gson gson;
+
+  @Rule
+  public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort());
+
+  @Before
+  public void setup() {
+    this.httpRecordSender =
+        new HttpRecordSender<>(URI.create("http://localhost:" + this.wireMockRule.port()));
+    this.gson = new Gson();
+  }
+
+  @Test
+  public void testValidUri() {
+    this.wireMockRule.stubFor(
+        post(urlPathEqualTo("/"))
+            .willReturn(
+                aResponse()
+                    .withStatus(200)
+                    .withBody("received")));
+
+    final ActivePowerRecord record = new ActivePowerRecord("my-id", 12345L, 12.34);
+    this.httpRecordSender.send(record);
+
+    final String expectedJson = "{\"identifier\":\"my-id\",\"timestamp\":12345,\"valueInW\":12.34}";
+    verify(exactly(1), postRequestedFor(urlEqualTo("/"))
+        .withRequestBody(equalTo(expectedJson))); // toJson
+  }
+
+}
diff --git a/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/KeySpaceTest.java b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/KeySpaceTest.java
index 20c094ddcc7ff110a25aaffa494766e89d4d2475..49004839a9c8fd280aba5006a1f08c2acb3c3136 100644
--- a/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/KeySpaceTest.java
+++ b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/KeySpaceTest.java
@@ -2,7 +2,6 @@ package theodolite.commons.workloadgeneration;
 
 import org.junit.Assert;
 import org.junit.Test;
-import theodolite.commons.workloadgeneration.KeySpace;
 
 public class KeySpaceTest {
 
diff --git a/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/LoadGeneratorTargetTest.java b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/LoadGeneratorTargetTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..644ffad9a4d2732f72ac307294d1311eba3a9ce8
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/LoadGeneratorTargetTest.java
@@ -0,0 +1,26 @@
+package theodolite.commons.workloadgeneration;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LoadGeneratorTargetTest {
+
+  @Test
+  public void testFromKafka() {
+    final LoadGeneratorTarget target = LoadGeneratorTarget.from("kafka");
+    Assert.assertEquals(LoadGeneratorTarget.KAFKA, target);
+  }
+
+  @Test
+  public void testFromHttp() {
+    final LoadGeneratorTarget target = LoadGeneratorTarget.from("http");
+    Assert.assertEquals(LoadGeneratorTarget.HTTP, target);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFromInvalidTarget() {
+    LoadGeneratorTarget.from("<invalid-target>");
+  }
+
+
+}
diff --git a/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorTest.java b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..04ba38b9c8fcd41df46d3d3070a6308acfd72cb7
--- /dev/null
+++ b/theodolite-benchmarks/load-generator-commons/src/test/java/theodolite/commons/workloadgeneration/TitanRecordGeneratorTest.java
@@ -0,0 +1,40 @@
+package theodolite.commons.workloadgeneration;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import org.junit.Assert;
+import org.junit.Test;
+import titan.ccp.model.records.ActivePowerRecord;
+
+public class TitanRecordGeneratorTest {
+
+  @Test
+  public void testGenerate() {
+    final ZoneId zoneId = ZoneOffset.UTC;
+    final LocalDateTime dateTime = LocalDateTime.of(2022, 1, 17, 14, 2, 42);
+    final Instant instant = dateTime.atZone(zoneId).toInstant();
+    final TitanRecordGenerator generator =
+        new TitanRecordGenerator(42.0, Clock.fixed(instant, zoneId));
+
+    final ActivePowerRecord activePowerRecord = generator.generate("my-identifier");
+    Assert.assertEquals("my-identifier", activePowerRecord.getIdentifier());
+    Assert.assertEquals(instant.toEpochMilli(), activePowerRecord.getTimestamp());
+    Assert.assertEquals(42.0, activePowerRecord.getValueInW(), 0.001);
+  }
+
+  @Test
+  public void testTimestampForArbitraryClockTimeZone() {
+    final LocalDateTime dateTime = LocalDateTime.of(2022, 1, 17, 14, 2, 42);
+    final Instant instant = dateTime.atZone(ZoneId.of("Europe/Paris")).toInstant();
+    // Setting of ZoneId should have no impact on result as we request epoch millis
+    final Clock clock = Clock.fixed(instant, ZoneId.of("America/Sao_Paulo"));
+    final TitanRecordGenerator generator = new TitanRecordGenerator(42.0, clock);
+
+    final ActivePowerRecord activePowerRecord = generator.generate("my-identifier");
+    Assert.assertEquals(instant.toEpochMilli(), activePowerRecord.getTimestamp());
+  }
+
+}
diff --git a/theodolite-benchmarks/settings.gradle b/theodolite-benchmarks/settings.gradle
index 5602e816bb21dce72162b085de99836b8f9aea1e..ae4254e968a0bc09970752f95c6a40db86ae775c 100644
--- a/theodolite-benchmarks/settings.gradle
+++ b/theodolite-benchmarks/settings.gradle
@@ -3,19 +3,34 @@ rootProject.name = 'theodolite-benchmarks'
 include 'load-generator-commons'
 include 'kstreams-commons'
 include 'flink-commons'
+include 'beam-commons'
+
+include 'uc1-beam'
+include 'uc2-beam'
+include 'uc3-beam'
+include 'uc4-beam'
 
 include 'uc1-load-generator'
 include 'uc1-kstreams'
 include 'uc1-flink'
+include 'uc1-beam-flink'
+include 'uc1-beam-samza'
 
 include 'uc2-load-generator'
 include 'uc2-kstreams'
 include 'uc2-flink'
+include 'uc2-beam-flink'
+include 'uc2-beam-samza'
 
 include 'uc3-load-generator'
 include 'uc3-kstreams'
 include 'uc3-flink'
+include 'uc3-beam-flink'
+include 'uc3-beam-samza'
 
 include 'uc4-load-generator'
 include 'uc4-kstreams'
 include 'uc4-flink'
+include 'uc4-beam-flink'
+include 'uc4-beam-samza'
+
diff --git a/theodolite-benchmarks/uc1-beam-flink/Dockerfile b/theodolite-benchmarks/uc1-beam-flink/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..0e3c0b3184e4bac8b62b97bc022df88c7701b619
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-flink/Dockerfile
@@ -0,0 +1,5 @@
+FROM flink:1.13-java11
+
+ADD build/distributions/uc1-beam-flink.tar /opt/flink/usrlib/artifacts/uc1-beam-flink.tar
+
+
diff --git a/theodolite-benchmarks/uc1-beam-flink/build.gradle b/theodolite-benchmarks/uc1-beam-flink/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f4b6cff8efbcdbcb701f249220643669f0f89626
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-flink/build.gradle
@@ -0,0 +1,9 @@
+plugins {
+  id 'theodolite.beam.flink'
+}
+
+dependencies {
+      implementation project(':uc1-beam')
+}
+
+mainClassName = "application.Uc1BeamFlink"
diff --git a/theodolite-benchmarks/uc1-beam-flink/src/main/java/application/Uc1BeamFlink.java b/theodolite-benchmarks/uc1-beam-flink/src/main/java/application/Uc1BeamFlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe58369b3c0c19351bcc5cde170df68946af7cbd
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-flink/src/main/java/application/Uc1BeamFlink.java
@@ -0,0 +1,40 @@
+package application;
+
+import org.apache.beam.runners.flink.FlinkRunner;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Database Storage using Apache Beam with the Flink Runner. To
+ * execute locally in standalone start Kafka, Zookeeper, the schema-registry and the workload
+ * generator using the delayed_startup.sh script. Start a Flink cluster and pass its REST adress
+ * using--flinkMaster as run parameter. To persist logs add
+ * ${workspace_loc:/uc1-application-samza/eclipseConsoleLogs.log} as Output File under Standard
+ * Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc1BeamFlink extends AbstractBeamService {
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc1BeamFlink(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(FlinkRunner.class);
+  }
+
+  /**
+   * Main method.
+   */
+  public static void main(final String[] args) {
+
+    // Create application via configurations
+    final Uc1BeamFlink uc1 = new Uc1BeamFlink(args);
+
+    // Create pipeline with configurations
+    final Uc1BeamPipeline pipeline = new Uc1BeamPipeline(uc1.options, uc1.getConfig());
+
+    // Submit job and start execution
+    pipeline.run().waitUntilFinish();
+  }
+
+}
+
diff --git a/theodolite-benchmarks/uc1-beam-flink/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc1-beam-flink/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..50db1510ab5d7f6b8c9b1a75f112719209c351ce
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-flink/src/main/resources/META-INF/application.properties
@@ -0,0 +1,16 @@
+application.name=theodolite-uc1-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+
+schema.registry.url=http://localhost:8081
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc1-beam-samza/.gitignore b/theodolite-benchmarks/uc1-beam-samza/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7bf05dd280fcc888467656ce1fbdeb65322c7ba8
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/.gitignore
@@ -0,0 +1 @@
+state
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc1-beam-samza/Dockerfile b/theodolite-benchmarks/uc1-beam-samza/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9b729060532ea3a242ac3084ba0bebf88ca2e9b6
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/Dockerfile
@@ -0,0 +1,9 @@
+FROM openjdk:11-slim
+
+ENV MAX_SOURCE_PARALLELISM=1024
+
+ADD build/distributions/uc1-beam-samza.tar /
+ADD samza-standalone.properties /
+
+CMD /uc1-beam-samza/bin/uc1-beam-samza --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory --configFilePath=samza-standalone.properties --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=$MAX_SOURCE_PARALLELISM --enableMetrics=false --configOverride="{\"job.coordinator.zk.connect\":\"$SAMZA_JOB_COORDINATOR_ZK_CONNECT\"}"
+
diff --git a/theodolite-benchmarks/uc1-beam-samza/build.gradle b/theodolite-benchmarks/uc1-beam-samza/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..54c070d967d17ccd41c85f90486655c9fd56b65b
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.samza'
+}
+
+dependencies {
+  implementation project(':uc1-beam')
+}
+
+
+mainClassName = "application.Uc1BeamSamza"
diff --git a/theodolite-benchmarks/uc1-beam-samza/samza-standalone.properties b/theodolite-benchmarks/uc1-beam-samza/samza-standalone.properties
new file mode 100644
index 0000000000000000000000000000000000000000..02411017e86e274f6057688a7c351567603c0f80
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/samza-standalone.properties
@@ -0,0 +1,17 @@
+# Set EnvironmentRewriter
+job.config.rewriters=env-config
+job.config.rewriter.env-config.class=org.apache.samza.config.EnvironmentConfigRewriter
+
+# Configure ZooKeeper for coordination
+job.coordinator.factory=org.apache.samza.zk.ZkJobCoordinatorFactory
+job.coordinator.zk.connect=localhost:2181
+
+# Use GroupByContainerIds
+task.name.grouper.factory=org.apache.samza.container.grouper.task.GroupByContainerIdsFactory
+
+# Configure Kafka as "system"
+job.default.system=kafka
+systems.kafka.samza.factory=org.apache.samza.system.kafka.KafkaSystemFactory
+systems.kafka.consumer.bootstrap.servers=localhost:9092
+systems.kafka.producer.bootstrap.servers=localhost:9092
+systems.kafka.default.stream.replication.factor=1
diff --git a/theodolite-benchmarks/uc1-beam-samza/src/main/java/application/Uc1BeamSamza.java b/theodolite-benchmarks/uc1-beam-samza/src/main/java/application/Uc1BeamSamza.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaef5c2d6968c4b89059537277a2582ecca70451
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/src/main/java/application/Uc1BeamSamza.java
@@ -0,0 +1,43 @@
+package application;
+
+import org.apache.beam.runners.samza.SamzaRunner;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Database Storage using Apache Beam with the Samza Runner. To
+ * execute locally in standalone start Kafka, Zookeeper, the schema-registry and the workload
+ * generator. Add
+ * --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory
+ * --configFilePath=samza-standalone.properties
+ * --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=1024 as program arguments. To
+ * persist logs add ${workspace_loc:/uc4-application-samza/eclipseConsoleLogs.log} as Output File
+ * under Standard Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc1BeamSamza extends AbstractBeamService {
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc1BeamSamza(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(SamzaRunner.class);
+  }
+
+  /**
+   * Main method.
+   */
+  public static void main(final String[] args) {
+
+    // Create application via configurations
+    final Uc1BeamSamza uc1 = new Uc1BeamSamza(args);
+
+    // Create pipeline with configurations
+    final Uc1BeamPipeline pipeline = new Uc1BeamPipeline(uc1.options, uc1.getConfig());
+
+    // Submit job and start execution
+    pipeline.run().waitUntilFinish();
+  }
+}
+
+
+
diff --git a/theodolite-benchmarks/uc1-beam-samza/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc1-beam-samza/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..50db1510ab5d7f6b8c9b1a75f112719209c351ce
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam-samza/src/main/resources/META-INF/application.properties
@@ -0,0 +1,16 @@
+application.name=theodolite-uc1-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+
+schema.registry.url=http://localhost:8081
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc1-beam/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc1-beam/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..da2db2cefa90c0d974068e22804132eb6c11d824
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,283 @@
+cleanup.add_all=false
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=true
+cleanup.always_use_this_for_non_static_method_access=true
+cleanup.array_with_curly=false
+cleanup.arrays_fill=false
+cleanup.bitwise_conditional_expression=false
+cleanup.boolean_literal=false
+cleanup.boolean_value_rather_than_comparison=true
+cleanup.break_loop=false
+cleanup.collection_cloning=false
+cleanup.comparing_on_criteria=false
+cleanup.comparison_statement=false
+cleanup.controlflow_merge=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=true
+cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
+cleanup.convert_to_switch_expressions=false
+cleanup.correct_indentation=true
+cleanup.do_while_rather_than_while=true
+cleanup.double_negation=false
+cleanup.else_if=false
+cleanup.embedded_if=false
+cleanup.evaluate_nullable=false
+cleanup.extract_increment=false
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.hash=false
+cleanup.if_condition=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.instanceof=false
+cleanup.instanceof_keyword=false
+cleanup.invert_equals=false
+cleanup.join=false
+cleanup.lazy_logical_operator=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.map_cloning=false
+cleanup.merge_conditional_blocks=false
+cleanup.multi_catch=false
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.no_string_creation=false
+cleanup.no_super=false
+cleanup.number_suffix=false
+cleanup.objects_equals=false
+cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=true
+cleanup.operand_factorization=false
+cleanup.organize_imports=true
+cleanup.overridden_assignment=false
+cleanup.plain_replacement=false
+cleanup.precompile_regex=false
+cleanup.primitive_comparison=false
+cleanup.primitive_parsing=false
+cleanup.primitive_rather_than_wrapper=true
+cleanup.primitive_serialization=false
+cleanup.pull_out_if_from_if_else=false
+cleanup.pull_up_assignment=false
+cleanup.push_down_negation=false
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.reduce_indentation=false
+cleanup.redundant_comparator=false
+cleanup.redundant_falling_through_block_end=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_modifiers=false
+cleanup.remove_redundant_semicolons=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_array_creation=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.return_expression=false
+cleanup.simplify_lambda_expression_and_method_ref=false
+cleanup.single_used_field=false
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.standard_comparison=false
+cleanup.static_inner_class=false
+cleanup.strictly_equal_or_different=false
+cleanup.stringbuffer_to_stringbuilder=false
+cleanup.stringbuilder=false
+cleanup.stringbuilder_for_local_vars=true
+cleanup.substring=false
+cleanup.switch=false
+cleanup.system_property=false
+cleanup.system_property_boolean=false
+cleanup.system_property_file_encoding=false
+cleanup.system_property_file_separator=false
+cleanup.system_property_line_separator=false
+cleanup.system_property_path_separator=false
+cleanup.ternary_operator=false
+cleanup.try_with_resource=false
+cleanup.unlooped_while=false
+cleanup.unreachable_block=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_autoboxing=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_directly_map_method=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_string_is_blank=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+cleanup.use_unboxing=false
+cleanup.use_var=false
+cleanup.useless_continue=false
+cleanup.useless_return=false
+cleanup.valueof_rather_than_instantiation=false
+cleanup_profile=_CAU-SE-Style
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_CAU-SE-Style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=true
+sp_cleanup.always_use_this_for_non_static_method_access=true
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=true
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/theodolite-benchmarks/uc1-beam/build.gradle b/theodolite-benchmarks/uc1-beam/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..502e94fa737fb2ae1bab861407b27575cd8766ca
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam/build.gradle
@@ -0,0 +1,5 @@
+plugins {
+  id 'theodolite.beam'
+}
+
+
diff --git a/theodolite-benchmarks/uc1-beam/src/main/java/application/LogKeyValue.java b/theodolite-benchmarks/uc1-beam/src/main/java/application/LogKeyValue.java
new file mode 100644
index 0000000000000000000000000000000000000000..79566fd937b9c100663d426610b6ff476035ef87
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam/src/main/java/application/LogKeyValue.java
@@ -0,0 +1,27 @@
+package application;
+
+import org.apache.beam.sdk.transforms.DoFn;
+import org.apache.beam.sdk.values.KV;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Logs all Key Value pairs.
+ */
+@SuppressWarnings({"unused"})
+public class LogKeyValue extends DoFn<KV<String, String>, KV<String, String>> {
+  private static final long serialVersionUID = 4328743;
+  private static final Logger LOGGER = LoggerFactory.getLogger(LogKeyValue.class);
+
+  /**
+   * Logs all key value pairs it processes.
+   */
+  @ProcessElement
+  public void processElement(@Element final KV<String, String> kv,
+      final OutputReceiver<KV<String, String>> out) {
+    if (LOGGER.isInfoEnabled()) {
+      LOGGER.info("Key: {}, Value: {}", kv.getKey(), kv.getValue());
+    }
+    out.output(kv);
+  }
+}
diff --git a/theodolite-benchmarks/uc1-beam/src/main/java/application/MapToGson.java b/theodolite-benchmarks/uc1-beam/src/main/java/application/MapToGson.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b0c6bc4ddfe78c22028da5b8cf7dde7ed57fced
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam/src/main/java/application/MapToGson.java
@@ -0,0 +1,26 @@
+package application;
+
+import com.google.gson.Gson;
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Converts a Map into a json String.
+ */
+public class MapToGson extends SimpleFunction<KV<String, ActivePowerRecord>, KV<String, String>> {
+  private static final long serialVersionUID = 7168356203579050214L;
+  private transient Gson gsonObj = new Gson();
+
+  @Override
+  public KV<String, String> apply(
+      final KV<String, ActivePowerRecord> kv) {
+
+    if (this.gsonObj == null) {
+      this.gsonObj = new Gson();
+    }
+
+    final String gson = this.gsonObj.toJson(kv.getValue());
+    return KV.of(kv.getKey(), gson);
+  }
+}
diff --git a/theodolite-benchmarks/uc1-beam/src/main/java/application/Uc1BeamPipeline.java b/theodolite-benchmarks/uc1-beam/src/main/java/application/Uc1BeamPipeline.java
new file mode 100644
index 0000000000000000000000000000000000000000..eaff08ac78cd18ddfd47eb2949ca13340ecc27b8
--- /dev/null
+++ b/theodolite-benchmarks/uc1-beam/src/main/java/application/Uc1BeamPipeline.java
@@ -0,0 +1,52 @@
+package application;
+
+import java.util.Map;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.CoderRegistry;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.transforms.MapElements;
+import org.apache.beam.sdk.transforms.ParDo;
+import org.apache.commons.configuration2.Configuration;
+import theodolite.commons.beam.AbstractPipeline;
+import theodolite.commons.beam.kafka.KafkaActivePowerTimestampReader;
+import titan.ccp.model.records.ActivePowerRecord;
+
+
+/**
+ * Implementation of the use case Database Storage using Apache Beam with the Flink Runner. To
+ * execute locally in standalone start Kafka, Zookeeper, the schema-registry and the workload
+ * generator using the delayed_startup.sh script. Start a Flink cluster and pass its REST adress
+ * using--flinkMaster as run parameter. To persist logs add
+ * ${workspace_loc:/uc1-application-samza/eclipseConsoleLogs.log} as Output File under Standard
+ * Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc1BeamPipeline extends AbstractPipeline {
+
+  protected Uc1BeamPipeline(final PipelineOptions options, final Configuration config) {
+    super(options, config);
+
+    // Set Coders for Classes that will be distributed
+    final CoderRegistry cr = this.getCoderRegistry();
+    cr.registerCoderForClass(ActivePowerRecord.class, AvroCoder.of(ActivePowerRecord.SCHEMA$));
+
+    // build KafkaConsumerConfig
+    final Map<String, Object> consumerConfig = this.buildConsumerConfig();
+
+    // Create Pipeline transformations
+    final KafkaActivePowerTimestampReader kafka =
+        new KafkaActivePowerTimestampReader(this.bootstrapServer, this.inputTopic, consumerConfig);
+
+    final LogKeyValue logKeyValue = new LogKeyValue();
+    final MapToGson mapToGson = new MapToGson();
+
+    // Apply pipeline transformations
+    // Read from Kafka
+    this.apply(kafka)
+        // Map to Gson
+        .apply(MapElements
+            .via(mapToGson))
+        // Print to console
+        .apply(ParDo.of(logKeyValue));
+  }
+}
+
diff --git a/theodolite-benchmarks/uc1-flink/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc1-flink/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/uc1-flink/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc1-flink/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc1-kstreams/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc1-kstreams/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/uc1-kstreams/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc1-kstreams/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc1-load-generator/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc1-load-generator/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc1-load-generator/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc1-load-generator/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc2-beam-flink/Dockerfile b/theodolite-benchmarks/uc2-beam-flink/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..b2ad8ff9036eef0f0cd0efad23d4f92abef7cc42
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-flink/Dockerfile
@@ -0,0 +1,4 @@
+FROM flink:1.13-java11
+
+ADD build/distributions/uc2-beam-flink.tar /opt/flink/usrlib/artifacts/uc2-beam-flink.tar
+
diff --git a/theodolite-benchmarks/uc2-beam-flink/build.gradle b/theodolite-benchmarks/uc2-beam-flink/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..9ab898cd465abe20e855d06ebf85373e46ab12e2
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-flink/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.flink'
+}
+
+
+dependencies {
+  implementation project(':uc2-beam')
+}
+
+mainClassName = "application.Uc2BeamFlink"
diff --git a/theodolite-benchmarks/uc2-beam-flink/src/main/java/application/Uc2BeamFlink.java b/theodolite-benchmarks/uc2-beam-flink/src/main/java/application/Uc2BeamFlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5bb849e626444929e00b17b1324a08c41cb19a0
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-flink/src/main/java/application/Uc2BeamFlink.java
@@ -0,0 +1,35 @@
+package application;
+
+import org.apache.beam.runners.flink.FlinkRunner;
+import org.apache.beam.sdk.Pipeline;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Downsampling using Apache Beam with the Flink Runner. To execute
+ * locally in standalone start Kafka, Zookeeper, the schema-registry and the workload generator
+ * using the delayed_startup.sh script. Start a Flink cluster and pass its REST adress
+ * using--flinkMaster as run parameter.
+ */
+public final class Uc2BeamFlink extends AbstractBeamService {
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc2BeamFlink(final String[] args) { // NOPMD
+    super(args);
+    this.options.setRunner(FlinkRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc2BeamFlink uc2BeamFlink = new Uc2BeamFlink(args);
+
+    final Pipeline pipeline = new Uc2BeamPipeline(uc2BeamFlink.options, uc2BeamFlink.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+}
+
diff --git a/theodolite-benchmarks/uc2-beam-flink/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc2-beam-flink/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1545a0f6630c8ea51d694f4056ca3aa750463f5b
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-flink/src/main/resources/META-INF/application.properties
@@ -0,0 +1,17 @@
+application.name=theodolite-uc2-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc2-beam-samza/.gitignore b/theodolite-benchmarks/uc2-beam-samza/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7bf05dd280fcc888467656ce1fbdeb65322c7ba8
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/.gitignore
@@ -0,0 +1 @@
+state
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc2-beam-samza/Dockerfile b/theodolite-benchmarks/uc2-beam-samza/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..22855cea279819cacbf6eee253c30c60409fdba3
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/Dockerfile
@@ -0,0 +1,8 @@
+FROM openjdk:11-slim
+
+ENV MAX_SOURCE_PARALLELISM=1024
+
+ADD build/distributions/uc2-beam-samza.tar /
+ADD samza-standalone.properties /
+
+CMD /uc2-beam-samza/bin/uc2-beam-samza --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory --configFilePath=samza-standalone.properties --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=$MAX_SOURCE_PARALLELISM --enableMetrics=false --configOverride="{\"job.coordinator.zk.connect\":\"$SAMZA_JOB_COORDINATOR_ZK_CONNECT\"}"
diff --git a/theodolite-benchmarks/uc2-beam-samza/build.gradle b/theodolite-benchmarks/uc2-beam-samza/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..29d7f9ac0c22c421072646ca665b3849c558d56f
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.samza'
+}
+
+
+dependencies {
+  implementation project(':uc2-beam')
+}
+
+mainClassName = "application.Uc2BeamSamza"
diff --git a/theodolite-benchmarks/uc2-beam-samza/samza-standalone.properties b/theodolite-benchmarks/uc2-beam-samza/samza-standalone.properties
new file mode 100644
index 0000000000000000000000000000000000000000..02411017e86e274f6057688a7c351567603c0f80
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/samza-standalone.properties
@@ -0,0 +1,17 @@
+# Set EnvironmentRewriter
+job.config.rewriters=env-config
+job.config.rewriter.env-config.class=org.apache.samza.config.EnvironmentConfigRewriter
+
+# Configure ZooKeeper for coordination
+job.coordinator.factory=org.apache.samza.zk.ZkJobCoordinatorFactory
+job.coordinator.zk.connect=localhost:2181
+
+# Use GroupByContainerIds
+task.name.grouper.factory=org.apache.samza.container.grouper.task.GroupByContainerIdsFactory
+
+# Configure Kafka as "system"
+job.default.system=kafka
+systems.kafka.samza.factory=org.apache.samza.system.kafka.KafkaSystemFactory
+systems.kafka.consumer.bootstrap.servers=localhost:9092
+systems.kafka.producer.bootstrap.servers=localhost:9092
+systems.kafka.default.stream.replication.factor=1
diff --git a/theodolite-benchmarks/uc2-beam-samza/src/main/java/application/Uc2BeamSamza.java b/theodolite-benchmarks/uc2-beam-samza/src/main/java/application/Uc2BeamSamza.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4b3d6d910824a718bffe8dc5f0204d53b9865c1
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/src/main/java/application/Uc2BeamSamza.java
@@ -0,0 +1,39 @@
+package application;
+
+import org.apache.beam.runners.samza.SamzaRunner;
+import org.apache.beam.sdk.Pipeline;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Downsampling using Apache Beam with the Samza Runner. To run
+ * locally in standalone start Kafka, Zookeeper, the schema-registry and the workload generator
+ * using the delayed_startup.sh script. Add
+ * --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory
+ * --configFilePath=${workspace_loc:uc3-application-samza}/config/standalone_local.properties
+ * --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=1024 --as program arguments. To
+ * persist logs add ${workspace_loc:/uc3-application-samza/eclipseConsoleLogs.log} as Output File
+ * under Standard Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc2BeamSamza extends AbstractBeamService {
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc2BeamSamza(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(SamzaRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc2BeamSamza uc2BeamSamza = new Uc2BeamSamza(args);
+
+    final Pipeline pipeline = new Uc2BeamPipeline(uc2BeamSamza.options, uc2BeamSamza.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+}
+
diff --git a/theodolite-benchmarks/uc2-beam-samza/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc2-beam-samza/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1545a0f6630c8ea51d694f4056ca3aa750463f5b
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam-samza/src/main/resources/META-INF/application.properties
@@ -0,0 +1,17 @@
+application.name=theodolite-uc2-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc2-beam/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc2-beam/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..43ccd3dbcdf80e49b8920c8fe242b35c3f604281
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,283 @@
+cleanup.add_all=false
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=true
+cleanup.always_use_this_for_non_static_method_access=true
+cleanup.array_with_curly=false
+cleanup.arrays_fill=false
+cleanup.bitwise_conditional_expression=false
+cleanup.boolean_literal=false
+cleanup.boolean_value_rather_than_comparison=true
+cleanup.break_loop=false
+cleanup.collection_cloning=false
+cleanup.comparing_on_criteria=false
+cleanup.comparison_statement=false
+cleanup.controlflow_merge=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=true
+cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
+cleanup.convert_to_switch_expressions=false
+cleanup.correct_indentation=true
+cleanup.do_while_rather_than_while=true
+cleanup.double_negation=false
+cleanup.else_if=false
+cleanup.embedded_if=false
+cleanup.evaluate_nullable=false
+cleanup.extract_increment=false
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.hash=false
+cleanup.if_condition=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.instanceof=false
+cleanup.instanceof_keyword=false
+cleanup.invert_equals=false
+cleanup.join=false
+cleanup.lazy_logical_operator=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.map_cloning=false
+cleanup.merge_conditional_blocks=false
+cleanup.multi_catch=false
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.no_string_creation=false
+cleanup.no_super=false
+cleanup.number_suffix=false
+cleanup.objects_equals=false
+cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=true
+cleanup.operand_factorization=false
+cleanup.organize_imports=true
+cleanup.overridden_assignment=false
+cleanup.plain_replacement=false
+cleanup.precompile_regex=false
+cleanup.primitive_comparison=false
+cleanup.primitive_parsing=false
+cleanup.primitive_rather_than_wrapper=true
+cleanup.primitive_serialization=false
+cleanup.pull_out_if_from_if_else=false
+cleanup.pull_up_assignment=false
+cleanup.push_down_negation=false
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.reduce_indentation=false
+cleanup.redundant_comparator=false
+cleanup.redundant_falling_through_block_end=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_modifiers=false
+cleanup.remove_redundant_semicolons=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_array_creation=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.return_expression=false
+cleanup.simplify_lambda_expression_and_method_ref=false
+cleanup.single_used_field=false
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.standard_comparison=false
+cleanup.static_inner_class=false
+cleanup.strictly_equal_or_different=false
+cleanup.stringbuffer_to_stringbuilder=false
+cleanup.stringbuilder=false
+cleanup.stringbuilder_for_local_vars=true
+cleanup.substring=false
+cleanup.switch=false
+cleanup.system_property=false
+cleanup.system_property_boolean=false
+cleanup.system_property_file_encoding=false
+cleanup.system_property_file_separator=false
+cleanup.system_property_line_separator=false
+cleanup.system_property_path_separator=false
+cleanup.ternary_operator=false
+cleanup.try_with_resource=false
+cleanup.unlooped_while=false
+cleanup.unreachable_block=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_autoboxing=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_directly_map_method=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_string_is_blank=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+cleanup.use_unboxing=false
+cleanup.use_var=false
+cleanup.useless_continue=false
+cleanup.useless_return=false
+cleanup.valueof_rather_than_instantiation=false
+cleanup_profile=_CAU-SE-Style
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_CAU-SE-Style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=true
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=false
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/theodolite-benchmarks/uc2-beam/build.gradle b/theodolite-benchmarks/uc2-beam/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..502e94fa737fb2ae1bab861407b27575cd8766ca
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam/build.gradle
@@ -0,0 +1,5 @@
+plugins {
+  id 'theodolite.beam'
+}
+
+
diff --git a/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsAggregation.java b/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsAggregation.java
new file mode 100644
index 0000000000000000000000000000000000000000..688f6677ec6d74e063a07a20c079b783aa71c399
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsAggregation.java
@@ -0,0 +1,45 @@
+package application;
+
+import com.google.common.math.Stats;
+import com.google.common.math.StatsAccumulator;
+import java.io.Serializable;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.DefaultCoder;
+import org.apache.beam.sdk.transforms.Combine.CombineFn;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Aggregation Class for ActivePowerRecords. Creates a StatsAccumulator based on the ValueInW.
+ */
+
+@DefaultCoder(AvroCoder.class)
+public class StatsAggregation extends CombineFn<ActivePowerRecord, StatsAccumulator, Stats>
+    implements Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  public StatsAccumulator createAccumulator() {
+    return new StatsAccumulator();
+  }
+
+  @Override
+  public StatsAccumulator addInput(final StatsAccumulator accum, final ActivePowerRecord input) {
+    accum.add(input.getValueInW());
+    return accum;
+  }
+
+  @Override
+  public StatsAccumulator mergeAccumulators(final Iterable<StatsAccumulator> accums) {
+    final StatsAccumulator merged = createAccumulator();
+    for (final StatsAccumulator accum : accums) {
+      merged.addAll(accum.snapshot());
+    }
+    return merged;
+  }
+
+  @Override
+  public Stats extractOutput(final StatsAccumulator accum) {
+    return accum.snapshot();
+  }
+}
diff --git a/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsToString.java b/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsToString.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a73ae8e1681b2c350dee328cba7283cf3386fd7
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam/src/main/java/application/StatsToString.java
@@ -0,0 +1,18 @@
+package application;
+
+import com.google.common.math.Stats;
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+
+/**
+ * Transforms a {@code KV<String, Stats>} into a {@code KV<String, String>}.
+ */
+public class StatsToString extends SimpleFunction<KV<String, Stats>, KV<String, String>> {
+  private static final long serialVersionUID = 4308991244493097240L;
+
+  @Override
+  public KV<String, String> apply(final KV<String, Stats> kv) {
+    return KV.of(kv.getKey(), kv.getValue().toString());
+  }
+
+}
diff --git a/theodolite-benchmarks/uc2-beam/src/main/java/application/Uc2BeamPipeline.java b/theodolite-benchmarks/uc2-beam/src/main/java/application/Uc2BeamPipeline.java
new file mode 100644
index 0000000000000000000000000000000000000000..02eec9868b0bbfbf6fd45206ff0d4092ac09e1ac
--- /dev/null
+++ b/theodolite-benchmarks/uc2-beam/src/main/java/application/Uc2BeamPipeline.java
@@ -0,0 +1,73 @@
+package application;
+
+import com.google.common.math.Stats;
+import com.google.common.math.StatsAccumulator;
+import java.util.Map;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.CoderRegistry;
+import org.apache.beam.sdk.coders.KvCoder;
+import org.apache.beam.sdk.coders.SerializableCoder;
+import org.apache.beam.sdk.coders.StringUtf8Coder;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.transforms.Combine;
+import org.apache.beam.sdk.transforms.MapElements;
+import org.apache.beam.sdk.transforms.windowing.FixedWindows;
+import org.apache.beam.sdk.transforms.windowing.Window;
+import org.apache.beam.sdk.values.KV;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.joda.time.Duration;
+import theodolite.commons.beam.AbstractPipeline;
+import theodolite.commons.beam.ConfigurationKeys;
+import theodolite.commons.beam.kafka.KafkaActivePowerTimestampReader;
+import theodolite.commons.beam.kafka.KafkaWriterTransformation;
+import titan.ccp.model.records.ActivePowerRecord;
+
+
+/**
+ * Implementation of the use case Downsampling using Apache Beam.
+ */
+public final class Uc2BeamPipeline extends AbstractPipeline {
+
+  protected Uc2BeamPipeline(final PipelineOptions options, final Configuration config) {
+    super(options, config);
+    // Additional needed variables
+    final String outputTopic = config.getString(ConfigurationKeys.KAFKA_OUTPUT_TOPIC);
+
+    final Duration duration =
+        Duration.standardMinutes(config.getInt(ConfigurationKeys.KAFKA_WINDOW_DURATION_MINUTES));
+
+    // Build kafka configuration
+    final Map<String, Object> consumerConfig = buildConsumerConfig();
+
+    // Set Coders for Classes that will be distributed
+    final CoderRegistry cr = getCoderRegistry();
+    cr.registerCoderForClass(ActivePowerRecord.class, AvroCoder.of(ActivePowerRecord.SCHEMA$));
+    cr.registerCoderForClass(StatsAggregation.class, SerializableCoder.of(StatsAggregation.class));
+    cr.registerCoderForClass(StatsAccumulator.class, AvroCoder.of(StatsAccumulator.class));
+
+    // Read from Kafka
+    final KafkaActivePowerTimestampReader kafkaActivePowerRecordReader =
+        new KafkaActivePowerTimestampReader(bootstrapServer, inputTopic, consumerConfig);
+
+    // Transform into String
+    final StatsToString statsToString = new StatsToString();
+
+    // Write to Kafka
+    final KafkaWriterTransformation<String> kafkaWriter =
+        new KafkaWriterTransformation<>(bootstrapServer, outputTopic, StringSerializer.class);
+
+    // Apply pipeline transformations
+    this.apply(kafkaActivePowerRecordReader)
+        // Apply a fixed window
+        .apply(Window.<KV<String, ActivePowerRecord>>into(FixedWindows.of(duration)))
+        // Aggregate per window for every key
+        .apply(Combine.<String, ActivePowerRecord, Stats>perKey(new StatsAggregation()))
+        .setCoder(KvCoder.of(StringUtf8Coder.of(), SerializableCoder.of(Stats.class)))
+        // Map into correct output format
+        .apply(MapElements.via(statsToString))
+        // Write to Kafka
+        .apply(kafkaWriter);
+  }
+}
+
diff --git a/theodolite-benchmarks/uc2-flink/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc2-flink/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc2-flink/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc2-flink/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc2-kstreams/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc2-kstreams/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc2-kstreams/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc2-kstreams/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc2-load-generator/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc2-load-generator/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc2-load-generator/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc2-load-generator/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc3-beam-flink/Dockerfile b/theodolite-benchmarks/uc3-beam-flink/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..3c0b4d96b8a59e45da3b85e8d7cd238b98cacfd7
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-flink/Dockerfile
@@ -0,0 +1,4 @@
+FROM flink:1.13-java11
+
+ADD build/distributions/uc3-beam-flink.tar /opt/flink/usrlib/artifacts/uc3-beam-flink.tar
+
diff --git a/theodolite-benchmarks/uc3-beam-flink/build.gradle b/theodolite-benchmarks/uc3-beam-flink/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..8f047c6dce50636f01a7cdf645722aa5f7ac9ce9
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-flink/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+  id 'theodolite.beam.flink'
+}
+
+
+dependencies {
+  implementation project(':uc3-beam')
+}
+
+
+// This is the path of the main class, stored within ./src/main/java/
+mainClassName = 'application.Uc3BeamFlink'
diff --git a/theodolite-benchmarks/uc3-beam-flink/src/main/java/application/Uc3BeamFlink.java b/theodolite-benchmarks/uc3-beam-flink/src/main/java/application/Uc3BeamFlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..18532b2655fcc6c24dad5f2fca87607c0b5d2e54
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-flink/src/main/java/application/Uc3BeamFlink.java
@@ -0,0 +1,39 @@
+package application;
+
+import org.apache.beam.runners.flink.FlinkRunner;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Aggregation based on Time Attributes using Apache Beam with the
+ * Flink Runner. To run locally in standalone start Kafka, Zookeeper, the schema-registry and the
+ * workload generator using the delayed_startup.sh script. And configure the Kafka, Zookeeper and
+ * Schema Registry urls accordingly. Start a Flink cluster and pass its REST adress
+ * using--flinkMaster as run parameter. To persist logs add
+ * ${workspace_loc:/uc4-application-samza/eclipseConsoleLogs.log} as Output File under Standard
+ * Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc3BeamFlink extends AbstractBeamService {
+
+  /**
+   * Private constructor to avoid instantiation.
+   */
+  private Uc3BeamFlink(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(FlinkRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc3BeamFlink uc3BeamFlink = new Uc3BeamFlink(args);
+
+    final Uc3BeamPipeline pipeline =
+        new Uc3BeamPipeline(uc3BeamFlink.options, uc3BeamFlink.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+
+}
+
diff --git a/theodolite-benchmarks/uc3-beam-flink/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc3-beam-flink/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2db723927eaee10d39e02a6b2d369a06af7711fc
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-flink/src/main/resources/META-INF/application.properties
@@ -0,0 +1,22 @@
+application.name=theodolite-uc3-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+aggregation.duration.days=30
+aggregation.advance.days=1
+
+trigger.interval=15
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc3-beam-samza/.gitignore b/theodolite-benchmarks/uc3-beam-samza/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7bf05dd280fcc888467656ce1fbdeb65322c7ba8
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/.gitignore
@@ -0,0 +1 @@
+state
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc3-beam-samza/Dockerfile b/theodolite-benchmarks/uc3-beam-samza/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d3e860bd72c54121d616bc5562d519e6e1e21dec
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/Dockerfile
@@ -0,0 +1,8 @@
+FROM openjdk:11-slim
+
+ENV MAX_SOURCE_PARALLELISM=1024
+
+ADD build/distributions/uc3-beam-samza.tar /
+ADD samza-standalone.properties /
+
+CMD /uc3-beam-samza/bin/uc3-beam-samza --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory --configFilePath=samza-standalone.properties --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=$MAX_SOURCE_PARALLELISM --enableMetrics=false --configOverride="{\"job.coordinator.zk.connect\":\"$SAMZA_JOB_COORDINATOR_ZK_CONNECT\"}"
diff --git a/theodolite-benchmarks/uc3-beam-samza/build.gradle b/theodolite-benchmarks/uc3-beam-samza/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..513b850330f4f71f440ad5da4ecea95f092f5ccc
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.samza'
+}
+
+
+dependencies {
+  implementation project(':uc3-beam')
+}
+
+mainClassName = "application.Uc3BeamSamza"
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc3-beam-samza/samza-standalone.properties b/theodolite-benchmarks/uc3-beam-samza/samza-standalone.properties
new file mode 100644
index 0000000000000000000000000000000000000000..02411017e86e274f6057688a7c351567603c0f80
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/samza-standalone.properties
@@ -0,0 +1,17 @@
+# Set EnvironmentRewriter
+job.config.rewriters=env-config
+job.config.rewriter.env-config.class=org.apache.samza.config.EnvironmentConfigRewriter
+
+# Configure ZooKeeper for coordination
+job.coordinator.factory=org.apache.samza.zk.ZkJobCoordinatorFactory
+job.coordinator.zk.connect=localhost:2181
+
+# Use GroupByContainerIds
+task.name.grouper.factory=org.apache.samza.container.grouper.task.GroupByContainerIdsFactory
+
+# Configure Kafka as "system"
+job.default.system=kafka
+systems.kafka.samza.factory=org.apache.samza.system.kafka.KafkaSystemFactory
+systems.kafka.consumer.bootstrap.servers=localhost:9092
+systems.kafka.producer.bootstrap.servers=localhost:9092
+systems.kafka.default.stream.replication.factor=1
diff --git a/theodolite-benchmarks/uc3-beam-samza/src/main/java/application/Uc3BeamSamza.java b/theodolite-benchmarks/uc3-beam-samza/src/main/java/application/Uc3BeamSamza.java
new file mode 100644
index 0000000000000000000000000000000000000000..913293bd02cb16e14ee9d94ea0e161c74853e72a
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/src/main/java/application/Uc3BeamSamza.java
@@ -0,0 +1,39 @@
+package application;
+
+import org.apache.beam.runners.samza.SamzaRunner;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Aggregation based on Time Attributes using Apache Beam with the
+ * Samza Runner. To run locally in standalone start Kafka, Zookeeper, the schema-registry and the
+ * workload generator using the delayed_startup.sh script. And configure the Kafka, Zookeeper and
+ * Schema Registry urls accordingly. Start a Flink cluster and pass its REST adress
+ * using--flinkMaster as run parameter. To persist logs add
+ * ${workspace_loc:/uc4-application-samza/eclipseConsoleLogs.log} as Output File under Standard
+ * Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc3BeamSamza extends AbstractBeamService {
+
+  /**
+   * Private constructor to avoid instantiation.
+   */
+  private Uc3BeamSamza(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(SamzaRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc3BeamSamza uc3BeamSamza = new Uc3BeamSamza(args);
+
+    final Uc3BeamPipeline pipeline =
+        new Uc3BeamPipeline(uc3BeamSamza.options, uc3BeamSamza.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+
+}
+
diff --git a/theodolite-benchmarks/uc3-beam-samza/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc3-beam-samza/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2db723927eaee10d39e02a6b2d369a06af7711fc
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam-samza/src/main/resources/META-INF/application.properties
@@ -0,0 +1,22 @@
+application.name=theodolite-uc3-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+aggregation.duration.days=30
+aggregation.advance.days=1
+
+trigger.interval=15
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc3-beam/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc3-beam/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..d71754b65d8da3cee7e6e440f49aa833ddabae10
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,283 @@
+cleanup.add_all=false
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=true
+cleanup.always_use_this_for_non_static_method_access=true
+cleanup.array_with_curly=false
+cleanup.arrays_fill=false
+cleanup.bitwise_conditional_expression=false
+cleanup.boolean_literal=false
+cleanup.boolean_value_rather_than_comparison=true
+cleanup.break_loop=false
+cleanup.collection_cloning=false
+cleanup.comparing_on_criteria=false
+cleanup.comparison_statement=false
+cleanup.controlflow_merge=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=true
+cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
+cleanup.convert_to_switch_expressions=false
+cleanup.correct_indentation=true
+cleanup.do_while_rather_than_while=true
+cleanup.double_negation=false
+cleanup.else_if=false
+cleanup.embedded_if=false
+cleanup.evaluate_nullable=false
+cleanup.extract_increment=false
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.hash=false
+cleanup.if_condition=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.instanceof=false
+cleanup.instanceof_keyword=false
+cleanup.invert_equals=false
+cleanup.join=false
+cleanup.lazy_logical_operator=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.map_cloning=false
+cleanup.merge_conditional_blocks=false
+cleanup.multi_catch=false
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.no_string_creation=false
+cleanup.no_super=false
+cleanup.number_suffix=false
+cleanup.objects_equals=false
+cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=true
+cleanup.operand_factorization=false
+cleanup.organize_imports=true
+cleanup.overridden_assignment=false
+cleanup.plain_replacement=false
+cleanup.precompile_regex=false
+cleanup.primitive_comparison=false
+cleanup.primitive_parsing=false
+cleanup.primitive_rather_than_wrapper=true
+cleanup.primitive_serialization=false
+cleanup.pull_out_if_from_if_else=false
+cleanup.pull_up_assignment=false
+cleanup.push_down_negation=false
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.reduce_indentation=false
+cleanup.redundant_comparator=false
+cleanup.redundant_falling_through_block_end=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_modifiers=false
+cleanup.remove_redundant_semicolons=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_array_creation=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.return_expression=false
+cleanup.simplify_lambda_expression_and_method_ref=false
+cleanup.single_used_field=false
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.standard_comparison=false
+cleanup.static_inner_class=false
+cleanup.strictly_equal_or_different=false
+cleanup.stringbuffer_to_stringbuilder=false
+cleanup.stringbuilder=false
+cleanup.stringbuilder_for_local_vars=true
+cleanup.substring=false
+cleanup.switch=false
+cleanup.system_property=false
+cleanup.system_property_boolean=false
+cleanup.system_property_file_encoding=false
+cleanup.system_property_file_separator=false
+cleanup.system_property_line_separator=false
+cleanup.system_property_path_separator=false
+cleanup.ternary_operator=false
+cleanup.try_with_resource=false
+cleanup.unlooped_while=false
+cleanup.unreachable_block=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_autoboxing=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_directly_map_method=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_string_is_blank=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+cleanup.use_unboxing=false
+cleanup.use_var=false
+cleanup.useless_continue=false
+cleanup.useless_return=false
+cleanup.valueof_rather_than_instantiation=false
+cleanup_profile=_CAU-SE-Style
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_CAU-SE-Style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=true
+sp_cleanup.always_use_this_for_non_static_method_access=true
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=true
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/theodolite-benchmarks/uc3-beam/build.gradle b/theodolite-benchmarks/uc3-beam/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..502e94fa737fb2ae1bab861407b27575cd8766ca
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/build.gradle
@@ -0,0 +1,5 @@
+plugins {
+  id 'theodolite.beam'
+}
+
+
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKey.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..6db59dd65ee494157400b0f4c6eafbdd7655d402
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKey.java
@@ -0,0 +1,34 @@
+package application;
+
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.DefaultCoder;
+
+
+/**
+ * Composed key of an hour of the day and a sensor id.
+ */
+@DefaultCoder(AvroCoder.class)
+public class HourOfDayKey {
+
+  private final int hourOfDay;
+  private final String sensorId;
+
+  public HourOfDayKey(final int hourOfDay, final String sensorId) {
+    this.hourOfDay = hourOfDay;
+    this.sensorId = sensorId;
+  }
+
+  public int getHourOfDay() {
+    return this.hourOfDay;
+  }
+
+  public String getSensorId() {
+    return this.sensorId;
+  }
+
+  @Override
+  public String toString() {
+    return this.sensorId + ";" + this.hourOfDay;
+  }
+
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeyFactory.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeyFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..b993a0199bc13e9b416f9b9cb77a27635d7fe1e1
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeyFactory.java
@@ -0,0 +1,24 @@
+package application;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * {@link StatsKeyFactory} for {@link HourOfDayKey}.
+ */
+public class HourOfDayKeyFactory implements StatsKeyFactory<HourOfDayKey>, Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  public HourOfDayKey createKey(final String sensorId, final LocalDateTime dateTime) {
+    final int hourOfDay = dateTime.getHour();
+    return new HourOfDayKey(hourOfDay, sensorId);
+  }
+
+  @Override
+  public String getSensorId(final HourOfDayKey key) {
+    return key.getSensorId();
+  }
+
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeySerde.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeySerde.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0f8e0bbaf959154bfbab69d83da56c81d55802a
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayKeySerde.java
@@ -0,0 +1,32 @@
+package application;
+
+import org.apache.kafka.common.serialization.Serde;
+import titan.ccp.common.kafka.simpleserdes.BufferSerde;
+import titan.ccp.common.kafka.simpleserdes.ReadBuffer;
+import titan.ccp.common.kafka.simpleserdes.SimpleSerdes;
+import titan.ccp.common.kafka.simpleserdes.WriteBuffer;
+
+/**
+ * {@link BufferSerde} for a {@link HourOfDayKey}. Use the {@link #create()} method to create a new
+ * Kafka {@link Serde}.
+ */
+public class HourOfDayKeySerde implements BufferSerde<HourOfDayKey> {
+
+  @Override
+  public void serialize(final WriteBuffer buffer, final HourOfDayKey data) {
+    buffer.putInt(data.getHourOfDay());
+    buffer.putString(data.getSensorId());
+  }
+
+  @Override
+  public HourOfDayKey deserialize(final ReadBuffer buffer) {
+    final int hourOfDay = buffer.getInt();
+    final String sensorId = buffer.getString();
+    return new HourOfDayKey(hourOfDay, sensorId);
+  }
+
+  public static Serde<HourOfDayKey> create() {
+    return SimpleSerdes.create(new HourOfDayKeySerde());
+  }
+
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayWithStats.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayWithStats.java
new file mode 100644
index 0000000000000000000000000000000000000000..46232b3f13601d77f6cb7b13ea0bcdc31290357a
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDayWithStats.java
@@ -0,0 +1,19 @@
+package application;
+
+import com.google.common.math.Stats;
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+
+/**
+ * {@link SimpleFunction} that transforms into the sensorId and the Value.
+ */
+public class HourOfDayWithStats extends
+    SimpleFunction<KV<HourOfDayKey, Stats>, KV<String, String>> {
+  private static final long serialVersionUID = -7411154345437422919L;
+  private final HourOfDayKeyFactory keyFactory = new HourOfDayKeyFactory();
+
+  @Override
+  public KV<String, String> apply(final KV<HourOfDayKey, Stats> kv) {
+    return KV.of(keyFactory.getSensorId(kv.getKey()), kv.getValue().toString());
+  }
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDaykeyCoder.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDaykeyCoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..196408263ec29a1ec9b45375dd0b53a18e8f60b3
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/HourOfDaykeyCoder.java
@@ -0,0 +1,60 @@
+package application;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.coders.CoderException;
+import org.apache.kafka.common.serialization.Serde;
+
+/**
+ * Wrapper Class that encapsulates a HourOfDayKeySerde in a org.apache.beam.sdk.coders.Coder.
+ */
+public class HourOfDaykeyCoder extends Coder<HourOfDayKey> implements Serializable {
+  public static final long serialVersionUID = 4444444;
+  private static final boolean DETERMINISTIC = true;
+  private static final int VALUE_SIZE = 4;
+
+  private transient Serde<HourOfDayKey> innerSerde = HourOfDayKeySerde.create();
+
+  @Override
+  public void encode(final HourOfDayKey value, final OutputStream outStream)
+      throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = HourOfDayKeySerde.create();
+    }
+    final byte[] bytes = this.innerSerde.serializer().serialize("ser", value);
+    final byte[] sizeinBytes = ByteBuffer.allocate(VALUE_SIZE).putInt(bytes.length).array();
+    outStream.write(sizeinBytes);
+    outStream.write(bytes);
+  }
+
+  @Override
+  public HourOfDayKey decode(final InputStream inStream) throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = HourOfDayKeySerde.create();
+    }
+    final byte[] sizeinBytes = new byte[VALUE_SIZE];
+    inStream.read(sizeinBytes);
+    final int size = ByteBuffer.wrap(sizeinBytes).getInt();
+    final byte[] bytes = new byte[size];
+    inStream.read(bytes);
+    return this.innerSerde.deserializer().deserialize("deser", bytes);
+  }
+
+  @Override
+  public List<? extends Coder<?>> getCoderArguments() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void verifyDeterministic() throws NonDeterministicException {
+    if (!DETERMINISTIC) {
+      throw new NonDeterministicException(this, "This class is not deterministic!");
+    }
+  }
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/MapTimeFormat.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/MapTimeFormat.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d8897fb0fd76cd4eb145da6a7ce031f9f45d396
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/MapTimeFormat.java
@@ -0,0 +1,27 @@
+package application;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Changes the time format to us europe/paris time.
+ */
+public class MapTimeFormat
+    extends SimpleFunction<KV<String, ActivePowerRecord>, KV<HourOfDayKey, ActivePowerRecord>> {
+  private static final long serialVersionUID = -6597391279968647035L;
+  private final StatsKeyFactory<HourOfDayKey> keyFactory = new HourOfDayKeyFactory();
+  private final ZoneId zone = ZoneId.of("Europe/Paris");
+
+  @Override
+  public KV<HourOfDayKey, ActivePowerRecord> apply(
+      final KV<String, ActivePowerRecord> kv) {
+    final Instant instant = Instant.ofEpochMilli(kv.getValue().getTimestamp());
+    final LocalDateTime dateTime = LocalDateTime.ofInstant(instant, this.zone);
+    return KV.of(this.keyFactory.createKey(kv.getValue().getIdentifier(), dateTime),
+        kv.getValue());
+  }
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsAggregation.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsAggregation.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee5cfc48bcd42dec41dd2030ad3f4a730fd6ac85
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsAggregation.java
@@ -0,0 +1,45 @@
+package application;
+
+import com.google.common.math.Stats;
+import com.google.common.math.StatsAccumulator;
+import java.io.Serializable;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.DefaultCoder;
+import org.apache.beam.sdk.transforms.Combine.CombineFn;
+import titan.ccp.model.records.ActivePowerRecord;
+
+
+/**
+ * Aggregation Class for ActivePowerRecords. Creates a StatsAccumulator based on the ValueInW.
+ */
+
+@DefaultCoder(AvroCoder.class)
+public class StatsAggregation extends CombineFn<ActivePowerRecord, StatsAccumulator, Stats>
+    implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  public StatsAccumulator createAccumulator() {
+    return new StatsAccumulator();
+  }
+
+  @Override
+  public StatsAccumulator addInput(final StatsAccumulator accum, final ActivePowerRecord input) {
+    accum.add(input.getValueInW());
+    return accum;
+  }
+
+  @Override
+  public StatsAccumulator mergeAccumulators(final Iterable<StatsAccumulator> accums) {
+    final StatsAccumulator merged = this.createAccumulator();
+    for (final StatsAccumulator accum : accums) {
+      merged.addAll(accum.snapshot());
+    }
+    return merged;
+  }
+
+  @Override
+  public Stats extractOutput(final StatsAccumulator accum) {
+    return accum.snapshot();
+  }
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsKeyFactory.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsKeyFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..820168058f88ca21f8efcf61c7ebed60c08aa200
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/StatsKeyFactory.java
@@ -0,0 +1,17 @@
+package application;
+
+import java.time.LocalDateTime;
+
+/**
+ * Factory interface for creating a stats key from a sensor id and a {@link LocalDateTime} object
+ * and vice versa.
+ *
+ * @param <T> Type of the key
+ */
+public interface StatsKeyFactory<T> {
+
+  T createKey(String sensorId, LocalDateTime dateTime);
+
+  String getSensorId(T key);
+
+}
diff --git a/theodolite-benchmarks/uc3-beam/src/main/java/application/Uc3BeamPipeline.java b/theodolite-benchmarks/uc3-beam/src/main/java/application/Uc3BeamPipeline.java
new file mode 100644
index 0000000000000000000000000000000000000000..c402271777dd63026e1f1fb36855dad1a72e1136
--- /dev/null
+++ b/theodolite-benchmarks/uc3-beam/src/main/java/application/Uc3BeamPipeline.java
@@ -0,0 +1,102 @@
+package application;
+
+import com.google.common.math.Stats;
+import com.google.common.math.StatsAccumulator;
+import java.util.Map;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.CoderRegistry;
+import org.apache.beam.sdk.coders.KvCoder;
+import org.apache.beam.sdk.coders.SerializableCoder;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.transforms.Combine;
+import org.apache.beam.sdk.transforms.MapElements;
+import org.apache.beam.sdk.transforms.windowing.AfterProcessingTime;
+import org.apache.beam.sdk.transforms.windowing.AfterWatermark;
+import org.apache.beam.sdk.transforms.windowing.SlidingWindows;
+import org.apache.beam.sdk.transforms.windowing.Window;
+import org.apache.beam.sdk.values.KV;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.joda.time.Duration;
+import theodolite.commons.beam.AbstractPipeline;
+import theodolite.commons.beam.ConfigurationKeys;
+import theodolite.commons.beam.kafka.KafkaActivePowerTimestampReader;
+import theodolite.commons.beam.kafka.KafkaWriterTransformation;
+import titan.ccp.model.records.ActivePowerRecord;
+
+
+/**
+ * Implementation of the use case Aggregation based on Time Attributes using Apache Beam.
+ */
+public final class Uc3BeamPipeline extends AbstractPipeline {
+
+  protected Uc3BeamPipeline(final PipelineOptions options, final Configuration config) {
+    super(options, config);
+    // Additional needed variables
+    final String outputTopic = config.getString(ConfigurationKeys.KAFKA_OUTPUT_TOPIC);
+
+    final Duration duration =
+        Duration.standardDays(config.getInt(ConfigurationKeys.AGGREGATION_DURATION_DAYS));
+    final Duration aggregationAdvanceDuration =
+        Duration.standardDays(config.getInt(ConfigurationKeys.AGGREGATION_ADVANCE_DAYS));
+    final Duration triggerDelay =
+        Duration.standardSeconds(config.getInt(ConfigurationKeys.TRIGGER_INTERVAL));
+
+    // Build Kafka configuration
+    final Map<String, Object> consumerConfig = this.buildConsumerConfig();
+
+    // Set Coders for classes that will be distributed
+    final CoderRegistry cr = this.getCoderRegistry();
+    registerCoders(cr);
+
+    // Read from Kafka
+    final KafkaActivePowerTimestampReader kafka =
+        new KafkaActivePowerTimestampReader(this.bootstrapServer, this.inputTopic, consumerConfig);
+
+    // Map the time format
+    final MapTimeFormat mapTimeFormat = new MapTimeFormat();
+
+    // Get the stats per HourOfDay
+    final HourOfDayWithStats hourOfDayWithStats = new HourOfDayWithStats();
+
+    // Write to Kafka
+    final KafkaWriterTransformation<String> kafkaWriter =
+        new KafkaWriterTransformation<>(this.bootstrapServer, outputTopic, StringSerializer.class);
+
+    this.apply(kafka)
+        // Map to correct time format
+        .apply(MapElements.via(mapTimeFormat))
+        // Apply a sliding window
+        .apply(Window
+            .<KV<HourOfDayKey, ActivePowerRecord>>into(
+                SlidingWindows.of(duration).every(aggregationAdvanceDuration))
+            .triggering(AfterWatermark.pastEndOfWindow()
+                .withEarlyFirings(
+                    AfterProcessingTime.pastFirstElementInPane().plusDelayOf(triggerDelay)))
+            .withAllowedLateness(Duration.ZERO)
+            .accumulatingFiredPanes())
+
+        // Aggregate per window for every key
+        .apply(Combine.<HourOfDayKey, ActivePowerRecord, Stats>perKey(new StatsAggregation()))
+        .setCoder(KvCoder.of(new HourOfDaykeyCoder(), SerializableCoder.of(Stats.class)))
+
+        // Map into correct output format
+        .apply(MapElements.via(hourOfDayWithStats))
+        // Write to Kafka
+        .apply(kafkaWriter);
+  }
+
+
+  /**
+   * Registers all Coders for all needed Coders.
+   *
+   * @param cr CoderRegistry.
+   */
+  private static void registerCoders(final CoderRegistry cr) {
+    cr.registerCoderForClass(ActivePowerRecord.class, AvroCoder.of(ActivePowerRecord.SCHEMA$));
+    cr.registerCoderForClass(HourOfDayKey.class, new HourOfDaykeyCoder());
+    cr.registerCoderForClass(StatsAggregation.class, SerializableCoder.of(StatsAggregation.class));
+    cr.registerCoderForClass(StatsAccumulator.class, AvroCoder.of(StatsAccumulator.class));
+  }
+}
+
diff --git a/theodolite-benchmarks/uc3-flink/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc3-flink/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc3-flink/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc3-flink/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc3-kstreams/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc3-kstreams/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/uc3-kstreams/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc3-kstreams/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc3-load-generator/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc3-load-generator/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/uc3-load-generator/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc3-load-generator/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc4-beam-flink/Dockerfile b/theodolite-benchmarks/uc4-beam-flink/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9c238ddbaccbe9040571e18ac8ad8eef5b7ecf15
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-flink/Dockerfile
@@ -0,0 +1,4 @@
+FROM flink:1.13-java11
+
+ADD build/distributions/uc4-beam-flink.tar /opt/flink/usrlib/artifacts/uc4-beam-flink.tar
+
diff --git a/theodolite-benchmarks/uc4-beam-flink/build.gradle b/theodolite-benchmarks/uc4-beam-flink/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..7ace89a2275e29e22186f7b67dcb7816cc7a85d0
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-flink/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.flink'
+}
+
+
+dependencies {
+  implementation project(':uc4-beam')
+}
+
+mainClassName = 'application.Uc4BeamFlink'
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc4-beam-flink/src/main/java/application/Uc4BeamFlink.java b/theodolite-benchmarks/uc4-beam-flink/src/main/java/application/Uc4BeamFlink.java
new file mode 100644
index 0000000000000000000000000000000000000000..90f9a4a292e99526fa94c7dd512bdcec548fbb4f
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-flink/src/main/java/application/Uc4BeamFlink.java
@@ -0,0 +1,34 @@
+package application;
+
+import org.apache.beam.runners.flink.FlinkRunner;
+import org.apache.beam.sdk.Pipeline;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Hierarchical Aggregation using Apache Beam with the Flink
+ * Runner.
+ **/
+public final class Uc4BeamFlink extends AbstractBeamService {
+
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc4BeamFlink(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(FlinkRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc4BeamFlink uc4BeamFlink = new Uc4BeamFlink(args);
+
+    final Pipeline pipeline = new Uc4BeamPipeline(uc4BeamFlink.options, uc4BeamFlink.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam-flink/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc4-beam-flink/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..bc679580dadf969e181b6787e8287066426be7e2
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-flink/src/main/resources/META-INF/application.properties
@@ -0,0 +1,25 @@
+application.name=theodolite-uc4-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.configuration.topic=configuration
+kafka.feedback.topic=aggregation-feedback
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+aggregation.duration.days=30
+aggregation.advance.days=1
+
+trigger.interval=15
+grace.period.ms=270
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc4-beam-samza/.gitignore b/theodolite-benchmarks/uc4-beam-samza/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7bf05dd280fcc888467656ce1fbdeb65322c7ba8
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/.gitignore
@@ -0,0 +1 @@
+state
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc4-beam-samza/Dockerfile b/theodolite-benchmarks/uc4-beam-samza/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..ffb82ee09e99384d4914a0f86b6d9214fc161381
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/Dockerfile
@@ -0,0 +1,8 @@
+FROM openjdk:11-slim
+
+ENV MAX_SOURCE_PARALLELISM=1024
+
+ADD build/distributions/uc4-beam-samza.tar /
+ADD samza-standalone.properties /
+
+CMD /uc4-beam-samza/bin/uc4-beam-samza --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory --configFilePath=samza-standalone.properties --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=$MAX_SOURCE_PARALLELISM --enableMetrics=false --configOverride="{\"job.coordinator.zk.connect\":\"$SAMZA_JOB_COORDINATOR_ZK_CONNECT\"}"
diff --git a/theodolite-benchmarks/uc4-beam-samza/build.gradle b/theodolite-benchmarks/uc4-beam-samza/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..90dcc76b0cae5aeca61f76d0cb73347b2407408b
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/build.gradle
@@ -0,0 +1,10 @@
+plugins {
+  id 'theodolite.beam.samza'
+}
+
+dependencies {
+  implementation project(':uc4-beam')
+}
+
+
+mainClassName = "application.Uc4BeamSamza"
diff --git a/theodolite-benchmarks/uc4-beam-samza/samza-standalone.properties b/theodolite-benchmarks/uc4-beam-samza/samza-standalone.properties
new file mode 100644
index 0000000000000000000000000000000000000000..812a9784c25ea84922b8a0f96f17b0377cedf925
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/samza-standalone.properties
@@ -0,0 +1,23 @@
+# Set EnvironmentRewriter
+job.config.rewriters=env-config
+job.config.rewriter.env-config.class=org.apache.samza.config.EnvironmentConfigRewriter
+
+# Configure ZooKeeper for coordination
+job.coordinator.factory=org.apache.samza.zk.ZkJobCoordinatorFactory
+job.coordinator.zk.connect=localhost:2181
+
+# Use GroupByContainerIds
+task.name.grouper.factory=org.apache.samza.container.grouper.task.GroupByContainerIdsFactory
+
+# Configure Kafka as "system"
+job.default.system=kafka
+systems.kafka.samza.factory=org.apache.samza.system.kafka.KafkaSystemFactory
+systems.kafka.consumer.bootstrap.servers=localhost:9092
+systems.kafka.producer.bootstrap.servers=localhost:9092
+systems.kafka.default.stream.replication.factor=1
+
+# Configure serialization and stores
+serializers.registry.string.class=org.apache.samza.serializers.StringSerdeFactory
+stores.my-store.factory=org.apache.samza.storage.kv.RocksDbKeyValueStorageEngineFactory
+stores.my-store.key.serde=string
+stores.my-store.msg.serde=string
diff --git a/theodolite-benchmarks/uc4-beam-samza/src/main/java/application/Uc4BeamSamza.java b/theodolite-benchmarks/uc4-beam-samza/src/main/java/application/Uc4BeamSamza.java
new file mode 100644
index 0000000000000000000000000000000000000000..3894fa95f16253e0a165dde70bf25d4a4bee96cb
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/src/main/java/application/Uc4BeamSamza.java
@@ -0,0 +1,40 @@
+package application;
+
+import org.apache.beam.runners.samza.SamzaRunner;
+import org.apache.beam.sdk.Pipeline;
+import theodolite.commons.beam.AbstractBeamService;
+
+/**
+ * Implementation of the use case Hierarchical Aggregation using Apache Beam with the Samza
+ * Runner. To run locally in standalone start Kafka, Zookeeper, the schema-registry and the
+ * workload generator using the delayed_startup.sh script. Add
+ * --configFactory=org.apache.samza.config.factories.PropertiesConfigFactory
+ * --configFilePath=${workspace_loc:uc4-application-samza}/config/standalone_local.properties
+ * --samzaExecutionEnvironment=STANDALONE --maxSourceParallelism=1024 --as program arguments. To
+ * persist logs add ${workspace_loc:/uc4-application-samza/eclipseConsoleLogs.log} as Output File
+ * under Standard Input Output in Common in the Run Configuration Start via Eclipse Run.
+ */
+public final class Uc4BeamSamza extends AbstractBeamService {
+
+
+  /**
+   * Private constructor setting specific options for this use case.
+   */
+  private Uc4BeamSamza(final String[] args) { //NOPMD
+    super(args);
+    this.options.setRunner(SamzaRunner.class);
+  }
+
+  /**
+   * Start running this microservice.
+   */
+  public static void main(final String[] args) {
+
+    final Uc4BeamSamza uc4BeamSamza = new Uc4BeamSamza(args);
+
+    final Pipeline pipeline = new Uc4BeamPipeline(uc4BeamSamza.options, uc4BeamSamza.getConfig());
+
+    pipeline.run().waitUntilFinish();
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam-samza/src/main/resources/META-INF/application.properties b/theodolite-benchmarks/uc4-beam-samza/src/main/resources/META-INF/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..bc679580dadf969e181b6787e8287066426be7e2
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam-samza/src/main/resources/META-INF/application.properties
@@ -0,0 +1,25 @@
+application.name=theodolite-uc4-application
+application.version=0.0.1
+
+kafka.bootstrap.servers=localhost:9092
+kafka.input.topic=input
+kafka.output.topic=output
+kafka.configuration.topic=configuration
+kafka.feedback.topic=aggregation-feedback
+kafka.window.duration.minutes=1
+
+schema.registry.url=http://localhost:8081
+
+aggregation.duration.days=30
+aggregation.advance.days=1
+
+trigger.interval=15
+grace.period.ms=270
+
+num.threads=1
+commit.interval.ms=1000
+cache.max.bytes.buffering=-1
+
+specific.avro.reader=True
+enable.auto.commit.config=True
+auto.offset.reset.config=earliest
\ No newline at end of file
diff --git a/theodolite-benchmarks/uc4-beam/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc4-beam/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000000000000000000000000000000000000..32e18c393f53a0b0b3207bb896ec0e4211b27bf0
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,284 @@
+cleanup.add_all=false
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=false
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=false
+cleanup.always_use_this_for_non_static_field_access=true
+cleanup.always_use_this_for_non_static_method_access=true
+cleanup.array_with_curly=false
+cleanup.arrays_fill=false
+cleanup.bitwise_conditional_expression=false
+cleanup.boolean_literal=false
+cleanup.boolean_value_rather_than_comparison=true
+cleanup.break_loop=false
+cleanup.collection_cloning=false
+cleanup.comparing_on_criteria=false
+cleanup.comparison_statement=false
+cleanup.controlflow_merge=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=true
+cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
+cleanup.convert_to_switch_expressions=false
+cleanup.correct_indentation=true
+cleanup.do_while_rather_than_while=true
+cleanup.double_negation=false
+cleanup.else_if=false
+cleanup.embedded_if=false
+cleanup.evaluate_nullable=false
+cleanup.extract_increment=false
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.hash=false
+cleanup.if_condition=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.instanceof=false
+cleanup.instanceof_keyword=false
+cleanup.invert_equals=false
+cleanup.join=false
+cleanup.lazy_logical_operator=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.map_cloning=false
+cleanup.merge_conditional_blocks=false
+cleanup.multi_catch=false
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=true
+cleanup.no_string_creation=false
+cleanup.no_super=false
+cleanup.number_suffix=false
+cleanup.objects_equals=false
+cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=true
+cleanup.operand_factorization=false
+cleanup.organize_imports=true
+cleanup.overridden_assignment=false
+cleanup.plain_replacement=false
+cleanup.precompile_regex=false
+cleanup.primitive_comparison=false
+cleanup.primitive_parsing=false
+cleanup.primitive_rather_than_wrapper=true
+cleanup.primitive_serialization=false
+cleanup.pull_out_if_from_if_else=false
+cleanup.pull_up_assignment=false
+cleanup.push_down_negation=false
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.reduce_indentation=false
+cleanup.redundant_comparator=false
+cleanup.redundant_falling_through_block_end=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_modifiers=false
+cleanup.remove_redundant_semicolons=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_array_creation=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.return_expression=false
+cleanup.simplify_lambda_expression_and_method_ref=false
+cleanup.single_used_field=false
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.standard_comparison=false
+cleanup.static_inner_class=false
+cleanup.strictly_equal_or_different=false
+cleanup.stringbuffer_to_stringbuilder=false
+cleanup.stringbuilder=false
+cleanup.stringbuilder_for_local_vars=true
+cleanup.substring=false
+cleanup.switch=false
+cleanup.system_property=false
+cleanup.system_property_boolean=false
+cleanup.system_property_file_encoding=false
+cleanup.system_property_file_separator=false
+cleanup.system_property_line_separator=false
+cleanup.system_property_path_separator=false
+cleanup.ternary_operator=false
+cleanup.try_with_resource=false
+cleanup.unlooped_while=false
+cleanup.unreachable_block=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_autoboxing=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_directly_map_method=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_string_is_blank=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+cleanup.use_unboxing=false
+cleanup.use_var=false
+cleanup.useless_continue=false
+cleanup.useless_return=false
+cleanup.valueof_rather_than_instantiation=false
+cleanup_profile=_CAU-SE-Style
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_CAU-SE-Style
+formatter_settings_version=21
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=true
+sp_cleanup.always_use_this_for_non_static_method_access=true
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=false
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=false
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=false
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/theodolite-benchmarks/uc4-beam/build.gradle b/theodolite-benchmarks/uc4-beam/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..502e94fa737fb2ae1bab861407b27575cd8766ca
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/build.gradle
@@ -0,0 +1,5 @@
+plugins {
+  id 'theodolite.beam'
+}
+
+
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedActivePowerRecordEventTimePolicy.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedActivePowerRecordEventTimePolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..dad9eca7d8d50d1b85932ddaa7ffc99418a4b759
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedActivePowerRecordEventTimePolicy.java
@@ -0,0 +1,34 @@
+package application;
+
+import java.util.Optional;
+import org.apache.beam.sdk.io.kafka.KafkaRecord;
+import org.apache.beam.sdk.io.kafka.TimestampPolicy;
+import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
+import org.joda.time.Instant;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * TimeStampPolicy to use event time based on the timestamp of the record value.
+ */
+public class AggregatedActivePowerRecordEventTimePolicy
+    extends TimestampPolicy<String, AggregatedActivePowerRecord> {
+  protected Instant currentWatermark;
+
+  public AggregatedActivePowerRecordEventTimePolicy(final Optional<Instant> previousWatermark) {
+    super();
+    this.currentWatermark = previousWatermark.orElse(BoundedWindow.TIMESTAMP_MIN_VALUE);
+  }
+
+  @Override
+  public Instant getTimestampForRecord(final PartitionContext ctx,
+      final KafkaRecord<String, AggregatedActivePowerRecord> record) {
+    this.currentWatermark = new Instant(record.getKV().getValue().getTimestamp());
+    return this.currentWatermark;
+  }
+
+  @Override
+  public Instant getWatermark(final PartitionContext ctx) {
+    return this.currentWatermark;
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedToActive.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedToActive.java
new file mode 100644
index 0000000000000000000000000000000000000000..bddd43e1e09c54bcfc85e5cbb65d1a6487f53438
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/AggregatedToActive.java
@@ -0,0 +1,22 @@
+package application;
+
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.ActivePowerRecord;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Converts AggregatedActivePowerRecord to ActivePowerRecord.
+ */
+public class AggregatedToActive
+    extends SimpleFunction<KV<String, AggregatedActivePowerRecord>, KV<String, ActivePowerRecord>> {
+
+  private static final long serialVersionUID = -8275252527964065889L;
+
+  @Override
+  public KV<String, ActivePowerRecord> apply(
+      final KV<String, AggregatedActivePowerRecord> kv) {
+    return KV.of(kv.getKey(), new ActivePowerRecord(kv.getValue().getIdentifier(),
+        kv.getValue().getTimestamp(), kv.getValue().getSumInW()));
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/DuplicateAsFlatMap.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/DuplicateAsFlatMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b66082c91b87c246d8c834249d2bc82545766f5
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/DuplicateAsFlatMap.java
@@ -0,0 +1,69 @@
+package application;
+
+import com.google.common.base.MoreObjects;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import org.apache.beam.sdk.state.StateSpec;
+import org.apache.beam.sdk.state.StateSpecs;
+import org.apache.beam.sdk.state.ValueState;
+import org.apache.beam.sdk.transforms.DoFn;
+import org.apache.beam.sdk.values.KV;
+import org.apache.beam.sdk.values.PCollectionView;
+import titan.ccp.model.records.ActivePowerRecord;
+
+
+/**
+ * Duplicates the Kv containing the (Children,Parents) pair as a flat map.
+ */
+public class DuplicateAsFlatMap extends DoFn
+    <KV<String, ActivePowerRecord>, KV<SensorParentKey, ActivePowerRecord>> {
+  private static final long serialVersionUID = -5132355515723961647L;
+  @StateId("parents")
+  private final StateSpec<ValueState<Set<String>>> parents = StateSpecs.value();//NOPMD
+  private final PCollectionView<Map<String, Set<String>>> childParentPairMap;
+
+  public DuplicateAsFlatMap(final PCollectionView<Map<String, Set<String>>> childParentPairMap) {
+    super();
+    this.childParentPairMap = childParentPairMap;
+  }
+
+
+  /**
+   *  Generate a KV-pair for every child-parent match.
+   */
+  @ProcessElement
+  public void processElement(@Element final KV<String, ActivePowerRecord> kv,
+                             final OutputReceiver<KV<SensorParentKey, ActivePowerRecord>> out,
+                             @StateId("parents") final ValueState<Set<String>> state,
+                             final ProcessContext c) {
+
+    final ActivePowerRecord record = kv.getValue() == null ? null : kv.getValue();
+    final Set<String> newParents =
+        c.sideInput(childParentPairMap).get(kv.getKey()) == null
+            ? Collections.emptySet()
+            : c.sideInput(childParentPairMap).get(kv.getKey());
+    final Set<String> oldParents =
+        MoreObjects.firstNonNull(state.read(), Collections.emptySet());
+    // Forward new Pairs if they exist
+    if (!newParents.isEmpty()) {
+      for (final String parent : newParents) {
+
+        // Forward flat mapped record
+        final SensorParentKey key = new SensorParentKey(kv.getKey(), parent);
+        out.output(KV.of(key, record));
+      }
+    }
+    if (!newParents.equals(oldParents)) {
+      for (final String oldParent : oldParents) {
+        if (!newParents.contains(oldParent)) {
+          // Forward Delete
+          final SensorParentKey key = new SensorParentKey(kv.getKey(), oldParent);
+          out.output(KV.of(key, null));
+        }
+      }
+      state.write(newParents);
+    }
+  }
+}
+
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterEvents.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterEvents.java
new file mode 100644
index 0000000000000000000000000000000000000000..3588443393fdef2e0fd1bf5f1e7c497e5030cf77
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterEvents.java
@@ -0,0 +1,19 @@
+package application;
+
+import org.apache.beam.sdk.transforms.SerializableFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.configuration.events.Event;
+
+/**
+ * Filters for {@code Event.SENSOR_REGISTRY_CHANGED} and {@code Event.SENSOR_REGISTRY_STATUS}
+ * events.
+ */
+public class FilterEvents implements SerializableFunction<KV<Event, String>, Boolean> {
+  private static final long serialVersionUID = -2233447357614891559L;
+
+  @Override
+  public Boolean apply(final KV<Event, String> kv) {
+    return kv.getKey() == Event.SENSOR_REGISTRY_CHANGED
+        || kv.getKey() == Event.SENSOR_REGISTRY_STATUS;
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterNullValues.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterNullValues.java
new file mode 100644
index 0000000000000000000000000000000000000000..143294f1ff2bfeea77c40ce38cd10ce3eb44be49
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/FilterNullValues.java
@@ -0,0 +1,18 @@
+package application;
+
+import org.apache.beam.sdk.transforms.SerializableFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Filters {@code null} Values.
+ */
+public class FilterNullValues implements
+    SerializableFunction<KV<SensorParentKey, ActivePowerRecord>, Boolean> {
+  private static final long serialVersionUID = -6197352369880867482L;
+
+  @Override
+  public Boolean apply(final KV<SensorParentKey, ActivePowerRecord> kv) {
+    return kv.getValue() != null;
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/GenerateParentsFn.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/GenerateParentsFn.java
new file mode 100644
index 0000000000000000000000000000000000000000..68cf551af9c681c586ebc6026c043ae8c9befbc5
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/GenerateParentsFn.java
@@ -0,0 +1,60 @@
+package application;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.beam.sdk.transforms.DoFn;
+import org.apache.beam.sdk.values.KV;
+// import theodolite.uc2.streamprocessing.KeyValue;
+// import theodolite.uc2.streamprocessing.KeyValueIterator;
+import titan.ccp.configuration.events.Event;
+import titan.ccp.model.sensorregistry.AggregatedSensor;
+import titan.ccp.model.sensorregistry.Sensor;
+import titan.ccp.model.sensorregistry.SensorRegistry;
+
+/**
+ * DoFn class to generate a child-parent pair for every sensor in the hierarchy.
+ */
+public class GenerateParentsFn extends DoFn<KV<Event, String>, KV<String, Set<String>>> {
+
+  private static final long serialVersionUID = 958270648688932091L;
+
+  /**
+   * Transforms a parent [children] map of sensors to a child [parents] map.
+   *
+   * @param kv input map.
+   * @param out outputstream.
+   */
+  @ProcessElement
+  public void processElement(@Element final KV<Event, String> kv,
+      final OutputReceiver<KV<String, Set<String>>> out) {
+    final Map<String, Set<String>> childParentsPairs =
+        this.constructChildParentsPairs(SensorRegistry.fromJson(kv.getValue()));
+    final Iterator<Map.Entry<String, Set<String>>> it = childParentsPairs.entrySet().iterator();
+    while (it.hasNext()) {
+      final Map.Entry<String, Set<String>> pair = it.next();
+      out.output(KV.of(pair.getKey(), pair.getValue()));
+    }
+
+  }
+
+  private Map<String, Set<String>> constructChildParentsPairs(final SensorRegistry registry) {
+    return this.streamAllChildren(registry.getTopLevelSensor())
+        .collect(Collectors.<Sensor, String, Set<String>>toMap(
+            child -> child.getIdentifier(),
+            child -> child.getParent()
+                .map(p -> Stream.of(p.getIdentifier()).collect(Collectors.toSet()))
+                .orElse(Collections.<String>emptySet())));
+  }
+
+  private Stream<Sensor> streamAllChildren(final AggregatedSensor sensor) {
+    return sensor.getChildren().stream()
+        .flatMap(s -> Stream.concat(
+            Stream.of(s),
+            s instanceof AggregatedSensor ? this.streamAllChildren((AggregatedSensor) s)
+                : Stream.empty()));
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/RecordAggregation.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/RecordAggregation.java
new file mode 100644
index 0000000000000000000000000000000000000000..16fd411b44cc1f955b255be870215ac120bce193
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/RecordAggregation.java
@@ -0,0 +1,63 @@
+package application;
+
+import java.io.Serializable;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.DefaultCoder;
+import org.apache.beam.sdk.transforms.Combine.CombineFn;
+import titan.ccp.model.records.ActivePowerRecord;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+
+
+/**
+ * CombineFn to aggregate ActivePowerRecords into AggregatedActivePowerRecords.
+ */
+public class RecordAggregation
+    extends CombineFn<ActivePowerRecord, RecordAggregation.Accum, AggregatedActivePowerRecord> {
+
+  private static final long serialVersionUID = 4362213539553233529L;
+
+  /**
+   * Wrapper for an accumulation of records.
+   */
+  @DefaultCoder(AvroCoder.class)
+  public static class Accum implements Serializable {
+    private static final long serialVersionUID = 3701311203919534376L;
+    private long count;
+    private Double sum = 0.0;
+    private long timestamp;
+  }
+
+  @Override
+  public Accum createAccumulator() {
+    return new Accum();
+  }
+
+  @Override
+  public Accum addInput(final Accum mutableAccumulator, final ActivePowerRecord input) {
+    mutableAccumulator.count += 1;
+    mutableAccumulator.sum += input.getValueInW();
+    mutableAccumulator.timestamp = input.getTimestamp();
+    return mutableAccumulator;
+  }
+
+  @Override
+  public Accum mergeAccumulators(final Iterable<Accum> accumulators) {
+    final Accum merged = this.createAccumulator();
+    for (final Accum accumulator : accumulators) {
+      merged.count += accumulator.count;
+      merged.sum += accumulator.sum;
+      merged.timestamp = accumulator.timestamp;
+    }
+
+    return merged;
+  }
+
+  @Override
+  public AggregatedActivePowerRecord extractOutput(final Accum accumulator) {
+    final double average = accumulator.count == 0 ? 0.0 : accumulator.sum / accumulator.count;
+    return new AggregatedActivePowerRecord("", accumulator.timestamp, accumulator.count,
+        accumulator.sum, average);
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/SensorParentKey.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/SensorParentKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..546fc04c2de089a28d8f0fba86a7fbcd5c1cc0a8
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/SensorParentKey.java
@@ -0,0 +1,30 @@
+package application;
+
+/**
+ * A key consisting of the identifier of a sensor and an identifier of parent sensor.
+ */
+public class SensorParentKey {
+
+  private final String sensorIdentifier;
+
+  private final String parentIdentifier;
+
+  public SensorParentKey(final String sensorIdentifier, final String parentIdentifier) {
+    this.sensorIdentifier = sensorIdentifier;
+    this.parentIdentifier = parentIdentifier;
+  }
+
+  public String getSensor() {
+    return this.sensorIdentifier;
+  }
+
+  public String getParent() {
+    return this.parentIdentifier;
+  }
+
+  @Override
+  public String toString() {
+    return "{" + this.sensorIdentifier + ", " + this.parentIdentifier + "}";
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/SetIdForAggregated.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/SetIdForAggregated.java
new file mode 100644
index 0000000000000000000000000000000000000000..0279a26ed925408b5383be50a202f5c88cea53aa
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/SetIdForAggregated.java
@@ -0,0 +1,22 @@
+package application;
+
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Sets the identifier for new {@link AggregatedActivePowerRecord}.
+ */
+public class SetIdForAggregated extends
+    SimpleFunction<KV<String, AggregatedActivePowerRecord>, KV<String, AggregatedActivePowerRecord>> { // NOCS
+  private static final long serialVersionUID = 2148522605294086982L;
+
+  @Override
+  public KV<String, AggregatedActivePowerRecord> apply(
+      final KV<String, AggregatedActivePowerRecord> kv) {
+    final AggregatedActivePowerRecord record = new AggregatedActivePowerRecord(
+        kv.getKey(), kv.getValue().getTimestamp(), kv.getValue().getCount(),
+        kv.getValue().getSumInW(), kv.getValue().getAverageInW());
+    return KV.of(kv.getKey(), record);
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/SetKeyToGroup.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/SetKeyToGroup.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d8dc70583fb45e02a5a8091b92d724bb22b4a78
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/SetKeyToGroup.java
@@ -0,0 +1,20 @@
+package application;
+
+import org.apache.beam.sdk.transforms.SimpleFunction;
+import org.apache.beam.sdk.values.KV;
+import titan.ccp.model.records.ActivePowerRecord;
+
+/**
+ * Set the Key for a group of {@code ActivePowerRecords} to their Parent.
+ */
+public class SetKeyToGroup
+    extends SimpleFunction<KV<SensorParentKey, ActivePowerRecord>, KV<String, ActivePowerRecord>> {
+
+  private static final long serialVersionUID = 790215050768527L;
+
+  @Override
+  public KV<String, ActivePowerRecord> apply(
+      final KV<SensorParentKey, ActivePowerRecord> kv) {
+    return KV.of(kv.getKey().getParent(), kv.getValue());
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/Uc4BeamPipeline.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/Uc4BeamPipeline.java
new file mode 100644
index 0000000000000000000000000000000000000000..7179fe5da937280d5baf72cd73cc392ef15a60e0
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/Uc4BeamPipeline.java
@@ -0,0 +1,248 @@
+package application; // NOPMD
+
+import com.google.common.math.StatsAccumulator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.CoderRegistry;
+import org.apache.beam.sdk.coders.SetCoder;
+import org.apache.beam.sdk.coders.StringUtf8Coder;
+import org.apache.beam.sdk.io.kafka.KafkaIO;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.transforms.Combine;
+import org.apache.beam.sdk.transforms.Filter;
+import org.apache.beam.sdk.transforms.Flatten;
+import org.apache.beam.sdk.transforms.Latest;
+import org.apache.beam.sdk.transforms.MapElements;
+import org.apache.beam.sdk.transforms.ParDo;
+import org.apache.beam.sdk.transforms.View;
+import org.apache.beam.sdk.transforms.windowing.AfterPane;
+import org.apache.beam.sdk.transforms.windowing.AfterProcessingTime;
+import org.apache.beam.sdk.transforms.windowing.AfterWatermark;
+import org.apache.beam.sdk.transforms.windowing.FixedWindows;
+import org.apache.beam.sdk.transforms.windowing.Repeatedly;
+import org.apache.beam.sdk.transforms.windowing.Window;
+import org.apache.beam.sdk.values.KV;
+import org.apache.beam.sdk.values.PCollection;
+import org.apache.beam.sdk.values.PCollectionList;
+import org.apache.beam.sdk.values.PCollectionView;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.joda.time.Duration;
+import serialization.AggregatedActivePowerRecordCoder;
+import serialization.AggregatedActivePowerRecordDeserializer;
+import serialization.AggregatedActivePowerRecordSerializer;
+import serialization.EventCoder;
+import serialization.EventDeserializer;
+import serialization.SensorParentKeyCoder;
+import theodolite.commons.beam.AbstractPipeline;
+import theodolite.commons.beam.ConfigurationKeys;
+import theodolite.commons.beam.kafka.KafkaActivePowerTimestampReader;
+import theodolite.commons.beam.kafka.KafkaGenericReader;
+import theodolite.commons.beam.kafka.KafkaWriterTransformation;
+import titan.ccp.configuration.events.Event;
+import titan.ccp.model.records.ActivePowerRecord;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Implementation of the use case Hierarchical Aggregation using Apache Beam.
+ */
+public final class Uc4BeamPipeline extends AbstractPipeline {
+
+  protected Uc4BeamPipeline(final PipelineOptions options, final Configuration config) { // NOPMD
+    super(options, config);
+
+    // Additional needed variables
+    final String feedbackTopic = config.getString(ConfigurationKeys.KAFKA_FEEDBACK_TOPIC);
+    final String outputTopic = config.getString(ConfigurationKeys.KAFKA_OUTPUT_TOPIC);
+    final String configurationTopic = config.getString(ConfigurationKeys.KAFKA_CONFIGURATION_TOPIC);
+
+    final Duration duration =
+        Duration.standardSeconds(config.getInt(ConfigurationKeys.KAFKA_WINDOW_DURATION_MINUTES));
+    final Duration triggerDelay =
+        Duration.standardSeconds(config.getInt(ConfigurationKeys.TRIGGER_INTERVAL));
+    final Duration gracePeriod =
+        Duration.standardSeconds(config.getInt(ConfigurationKeys.GRACE_PERIOD_MS));
+
+    // Build kafka configuration
+    final Map<String, Object> consumerConfig = this.buildConsumerConfig();
+    final Map<String, Object> configurationConfig = this.configurationConfig(config);
+
+    // Set Coders for Classes that will be distributed
+    final CoderRegistry cr = this.getCoderRegistry();
+    registerCoders(cr);
+
+    // Read from Kafka
+    // ActivePowerRecords
+    final KafkaActivePowerTimestampReader kafkaActivePowerRecordReader =
+        new KafkaActivePowerTimestampReader(this.bootstrapServer, this.inputTopic, consumerConfig);
+
+    // Configuration Events
+    final KafkaGenericReader<Event, String> kafkaConfigurationReader =
+        new KafkaGenericReader<>(
+            this.bootstrapServer, configurationTopic, configurationConfig,
+            EventDeserializer.class, StringDeserializer.class);
+
+    // Transform into AggregatedActivePowerRecords into ActivePowerRecords
+    final AggregatedToActive aggregatedToActive = new AggregatedToActive();
+
+    // Write to Kafka
+    final KafkaWriterTransformation<AggregatedActivePowerRecord> kafkaOutput =
+        new KafkaWriterTransformation<>(
+            this.bootstrapServer, outputTopic, AggregatedActivePowerRecordSerializer.class);
+
+    final KafkaWriterTransformation<AggregatedActivePowerRecord> kafkaFeedback =
+        new KafkaWriterTransformation<>(
+            this.bootstrapServer, feedbackTopic, AggregatedActivePowerRecordSerializer.class);
+
+    // Apply pipeline transformations
+    final PCollection<KV<String, ActivePowerRecord>> values = this
+        .apply("Read from Kafka", kafkaActivePowerRecordReader)
+        .apply("Read Windows", Window.into(FixedWindows.of(duration)))
+        .apply("Set trigger for input", Window
+            .<KV<String, ActivePowerRecord>>configure()
+            .triggering(Repeatedly.forever(
+                AfterProcessingTime.pastFirstElementInPane()
+                    .plusDelayOf(triggerDelay)))
+            .withAllowedLateness(gracePeriod)
+            .discardingFiredPanes());
+
+    // Read the results of earlier aggregations.
+    final PCollection<KV<String, ActivePowerRecord>> aggregationsInput = this
+        .apply("Read aggregation results", KafkaIO.<String, AggregatedActivePowerRecord>read()
+            .withBootstrapServers(this.bootstrapServer)
+            .withTopic(feedbackTopic)
+            .withKeyDeserializer(StringDeserializer.class)
+            .withValueDeserializer(AggregatedActivePowerRecordDeserializer.class)
+            .withTimestampPolicyFactory(
+                (tp, previousWaterMark) -> new AggregatedActivePowerRecordEventTimePolicy(
+                    previousWaterMark))
+            .withoutMetadata())
+        .apply("Apply Windows", Window.into(FixedWindows.of(duration)))
+        // Convert into the correct data format
+        .apply("Convert AggregatedActivePowerRecord to ActivePowerRecord",
+            MapElements.via(aggregatedToActive))
+        .apply("Set trigger for feedback", Window
+            .<KV<String, ActivePowerRecord>>configure()
+            .triggering(Repeatedly.forever(
+                AfterProcessingTime.pastFirstElementInPane()
+                    .plusDelayOf(triggerDelay)))
+            .withAllowedLateness(gracePeriod)
+            .discardingFiredPanes());
+
+    // Prepare flatten
+    final PCollectionList<KV<String, ActivePowerRecord>> collections =
+        PCollectionList.of(values).and(aggregationsInput);
+
+    // Create a single PCollection out of the input and already computed results
+    final PCollection<KV<String, ActivePowerRecord>> inputCollection =
+        collections.apply("Flatten sensor data and aggregation results",
+            Flatten.pCollections());
+
+    // Build the configuration stream from a changelog.
+    final PCollection<KV<String, Set<String>>> configurationStream = this
+        .apply("Read sensor groups", kafkaConfigurationReader)
+        // Only forward relevant changes in the hierarchy
+        .apply("Filter changed and status events",
+            Filter.by(new FilterEvents()))
+        // Build the changelog
+        .apply("Generate Parents for every Sensor", ParDo.of(new GenerateParentsFn()))
+        .apply("Update child and parent pairs", ParDo.of(new UpdateChildParentPairs()))
+        .apply("Set trigger for configuration", Window
+            .<KV<String, Set<String>>>configure()
+            .triggering(AfterWatermark.pastEndOfWindow()
+                .withEarlyFirings(
+                    AfterPane.elementCountAtLeast(1)))
+            .withAllowedLateness(Duration.ZERO)
+            .accumulatingFiredPanes());
+
+    final PCollectionView<Map<String, Set<String>>> childParentPairMap =
+        configurationStream.apply(Latest.perKey())
+            // Reset trigger to avoid synchronized processing time
+            .apply("Reset trigger for configurations", Window
+                .<KV<String, Set<String>>>configure()
+                .triggering(AfterWatermark.pastEndOfWindow()
+                    .withEarlyFirings(
+                        AfterPane.elementCountAtLeast(1)))
+                .withAllowedLateness(Duration.ZERO)
+                .accumulatingFiredPanes())
+            .apply(View.asMap());
+
+    final FilterNullValues filterNullValues = new FilterNullValues();
+
+    // Build pairs of every sensor reading and parent
+    final PCollection<KV<SensorParentKey, ActivePowerRecord>> flatMappedValues =
+        inputCollection.apply(
+            "Duplicate as flatMap",
+            ParDo.of(new DuplicateAsFlatMap(childParentPairMap))
+                .withSideInputs(childParentPairMap))
+            .apply("Filter only latest changes", Latest.perKey())
+            .apply("Filter out null values",
+                Filter.by(filterNullValues));
+
+    final SetIdForAggregated setIdForAggregated = new SetIdForAggregated();
+    final SetKeyToGroup setKeyToGroup = new SetKeyToGroup();
+
+    // Aggregate for every sensor group of the current level
+    final PCollection<KV<String, AggregatedActivePowerRecord>> aggregations = flatMappedValues
+        .apply("Set key to group", MapElements.via(setKeyToGroup))
+        // Reset trigger to avoid synchronized processing time
+        .apply("Reset trigger for aggregations", Window
+            .<KV<String, ActivePowerRecord>>configure()
+            .triggering(Repeatedly.forever(
+                AfterProcessingTime.pastFirstElementInPane()
+                    .plusDelayOf(triggerDelay)))
+            .withAllowedLateness(gracePeriod)
+            .discardingFiredPanes())
+        .apply(
+            "Aggregate per group",
+            Combine.perKey(new RecordAggregation()))
+        .apply("Set the Identifier in AggregatedActivePowerRecord",
+            MapElements.via(setIdForAggregated));
+
+    aggregations.apply("Write to aggregation results", kafkaOutput);
+
+    aggregations
+        .apply("Write to feedback topic", kafkaFeedback);
+
+  }
+
+
+  /**
+   * Builds a simple configuration for a Kafka consumer transformation.
+   *
+   * @return the build configuration.
+   */
+  public Map<String, Object> configurationConfig(final Configuration config) {
+    final Map<String, Object> consumerConfig = new HashMap<>();
+    consumerConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,
+        config.getString(ConfigurationKeys.ENABLE_AUTO_COMMIT_CONFIG));
+    consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,
+        config
+            .getString(ConfigurationKeys.AUTO_OFFSET_RESET_CONFIG));
+
+    consumerConfig.put(ConsumerConfig.GROUP_ID_CONFIG, config
+        .getString(ConfigurationKeys.APPLICATION_NAME) + "-configuration");
+    return consumerConfig;
+  }
+
+
+  /**
+   * Registers all Coders for all needed Coders.
+   *
+   * @param cr CoderRegistry.
+   */
+  private static void registerCoders(final CoderRegistry cr) {
+    cr.registerCoderForClass(ActivePowerRecord.class,
+        AvroCoder.of(ActivePowerRecord.class));
+    cr.registerCoderForClass(AggregatedActivePowerRecord.class,
+        new AggregatedActivePowerRecordCoder());
+    cr.registerCoderForClass(Set.class, SetCoder.of(StringUtf8Coder.of()));
+    cr.registerCoderForClass(Event.class, new EventCoder());
+    cr.registerCoderForClass(SensorParentKey.class, new SensorParentKeyCoder());
+    cr.registerCoderForClass(StatsAccumulator.class, AvroCoder.of(StatsAccumulator.class));
+  }
+}
+
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/application/UpdateChildParentPairs.java b/theodolite-benchmarks/uc4-beam/src/main/java/application/UpdateChildParentPairs.java
new file mode 100644
index 0000000000000000000000000000000000000000..8692be5ae6637ebda86f10d66b43c6071264e099
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/application/UpdateChildParentPairs.java
@@ -0,0 +1,36 @@
+package application;
+
+import java.util.Set;
+import org.apache.beam.sdk.state.StateSpec;
+import org.apache.beam.sdk.state.StateSpecs;
+import org.apache.beam.sdk.state.ValueState;
+import org.apache.beam.sdk.transforms.DoFn;
+import org.apache.beam.sdk.values.KV;
+
+/**
+ * Forward changes or tombstone values for deleted records.
+ */
+public class UpdateChildParentPairs extends DoFn<KV<String, Set<String>>, KV<String, Set<String>>> {
+
+  private static final long serialVersionUID = 1L;
+
+  @StateId("parents")
+  private final StateSpec<ValueState<Set<String>>> parents = // NOPMD
+      StateSpecs.value();
+
+  /**
+   * Match the changes accordingly.
+   *
+   * @param kv the sensor parents set that contains the changes.
+   */
+  @ProcessElement
+  public void processElement(@Element final KV<String, Set<String>> kv,
+      final OutputReceiver<KV<String, Set<String>>> out,
+      @StateId("parents") final ValueState<Set<String>> state) {
+    if (kv.getValue() == null || !kv.getValue().equals(state.read())) {
+      out.output(kv);
+      state.write(kv.getValue());
+    }
+
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordCoder.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordCoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..d2b484f5ab30be63f311d6dbcf495baebbd5e2b4
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordCoder.java
@@ -0,0 +1,57 @@
+package serialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.List;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.coders.CoderException;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Wrapper Class that encapsulates a AggregatedActivePowerRecord Serde in a
+ * org.apache.beam.sdk.coders.Coder.
+ */
+@SuppressWarnings("serial")
+public class AggregatedActivePowerRecordCoder extends Coder<AggregatedActivePowerRecord>
+    implements Serializable {
+
+  private static final boolean DETERMINISTIC = true;
+
+  private transient AvroCoder<AggregatedActivePowerRecord> avroEnCoder =
+      AvroCoder.of(AggregatedActivePowerRecord.class);
+
+  @Override
+  public void encode(final AggregatedActivePowerRecord value, final OutputStream outStream)
+      throws CoderException, IOException {
+    if (this.avroEnCoder == null) {
+      this.avroEnCoder = AvroCoder.of(AggregatedActivePowerRecord.class);
+    }
+    this.avroEnCoder.encode(value, outStream);
+
+  }
+
+  @Override
+  public AggregatedActivePowerRecord decode(final InputStream inStream)
+      throws CoderException, IOException {
+    if (this.avroEnCoder == null) {
+      this.avroEnCoder = AvroCoder.of(AggregatedActivePowerRecord.class);
+    }
+    return this.avroEnCoder.decode(inStream);
+
+  }
+
+  @Override
+  public List<? extends Coder<?>> getCoderArguments() {
+    return null;
+  }
+
+  @Override
+  public void verifyDeterministic() throws NonDeterministicException {
+    if (!DETERMINISTIC) {
+      throw new NonDeterministicException(this, "This class should be deterministic!");
+    }
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordDeserializer.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e2f2765ff65d3bca2a127be36db0854f15afebc
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordDeserializer.java
@@ -0,0 +1,34 @@
+package serialization;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.kafka.common.serialization.Deserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Wrapper Class that encapsulates a IMonitoringRecordSerde.serializer in a Deserializer
+ */
+public class AggregatedActivePowerRecordDeserializer
+    implements Deserializer<AggregatedActivePowerRecord> {
+
+  private static final Logger LOGGER =
+      LoggerFactory.getLogger(AggregatedActivePowerRecordDeserializer.class);
+
+  private final transient AvroCoder<AggregatedActivePowerRecord> avroEnCoder =
+      AvroCoder.of(AggregatedActivePowerRecord.class);
+
+  @Override
+  public AggregatedActivePowerRecord deserialize(final String topic, final byte[] data) {
+    AggregatedActivePowerRecord value = null;
+    try {
+      value = this.avroEnCoder.decode(new ByteArrayInputStream(data));
+    } catch (final IOException e) {
+      LOGGER.error("Could not deserialize AggregatedActivePowerRecord", e);
+    }
+    return value;
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordSerializer.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..77b79d5465f1d561870bf5b04f8fa20f87076adb
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/AggregatedActivePowerRecordSerializer.java
@@ -0,0 +1,45 @@
+package serialization;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import org.apache.beam.sdk.coders.AvroCoder;
+import org.apache.kafka.common.serialization.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import titan.ccp.model.records.AggregatedActivePowerRecord;
+
+/**
+ * Wrapper Class that encapsulates a IMonitoringRecordSerde.serializer in a Serializer
+ */
+public class AggregatedActivePowerRecordSerializer
+    implements Serializer<AggregatedActivePowerRecord> {
+
+  private static final Logger LOGGER =
+      LoggerFactory.getLogger(AggregatedActivePowerRecordSerializer.class);
+
+  private final transient AvroCoder<AggregatedActivePowerRecord> avroEnCoder =
+      AvroCoder.of(AggregatedActivePowerRecord.class);
+
+  @Override
+  public byte[] serialize(final String topic, final AggregatedActivePowerRecord data) {
+    final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    try {
+      this.avroEnCoder.encode(data, out);
+    } catch (final IOException e) {
+      LOGGER.error("Could not serialize AggregatedActivePowerRecord", e);
+    }
+    final byte[] result = out.toByteArray();
+    try {
+      out.close();
+    } catch (final IOException e) {
+      LOGGER.error(
+          "Could not close output stream after serialization of AggregatedActivePowerRecord", e);
+    }
+    return result;
+  }
+
+  @Override
+  public void close() {
+    Serializer.super.close();
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventCoder.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventCoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..710beb71dc8776e6309028327b05307aa590a7f6
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventCoder.java
@@ -0,0 +1,63 @@
+package serialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.coders.CoderException;
+import org.apache.kafka.common.serialization.Serde;
+import titan.ccp.configuration.events.Event;
+import titan.ccp.configuration.events.EventSerde;
+
+/**
+ * Wrapper Class that encapsulates a Event Serde in a org.apache.beam.sdk.coders.Coder.
+ */
+public class EventCoder extends Coder<Event> implements Serializable {
+
+  private static final long serialVersionUID = 8403045343970659100L;
+  private static final int VALUE_SIZE = 4;
+  private static final boolean DETERMINISTIC = true;
+
+  private transient Serde<Event> innerSerde = EventSerde.serde();
+
+  @Override
+  public void encode(final Event value, final OutputStream outStream)
+      throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = EventSerde.serde();
+    }
+    final byte[] bytes = this.innerSerde.serializer().serialize("ser", value);
+    final byte[] sizeinBytes = ByteBuffer.allocate(VALUE_SIZE).putInt(bytes.length).array();
+    outStream.write(sizeinBytes);
+    outStream.write(bytes);
+  }
+
+  @Override
+  public Event decode(final InputStream inStream) throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = EventSerde.serde();
+    }
+    final byte[] sizeinBytes = new byte[VALUE_SIZE];
+    inStream.read(sizeinBytes);
+    final int size = ByteBuffer.wrap(sizeinBytes).getInt();
+    final byte[] bytes = new byte[size];
+    inStream.read(bytes);
+    return this.innerSerde.deserializer().deserialize("deser", bytes);
+  }
+
+  @Override
+  public List<? extends Coder<?>> getCoderArguments() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void verifyDeterministic() throws NonDeterministicException {
+    if (!DETERMINISTIC) {
+      throw new NonDeterministicException(this, "This class should be deterministic!");
+    }
+  }
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventDeserializer.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventDeserializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..34e31a3059d0749848a30979f32e6df6651c1b47
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/EventDeserializer.java
@@ -0,0 +1,36 @@
+package serialization;
+
+import java.util.Map;
+import org.apache.kafka.common.serialization.ByteBufferDeserializer;
+import org.apache.kafka.common.serialization.Deserializer;
+import titan.ccp.configuration.events.Event;
+
+/**
+ * Deserializer for Events(SensorRegistry changes).
+ */
+public class EventDeserializer implements Deserializer<Event> {
+
+  private final ByteBufferDeserializer byteBufferDeserializer = new ByteBufferDeserializer();
+
+  @Override
+  public void configure(final Map<String, ?> configs, final boolean isKey) {
+    this.byteBufferDeserializer.configure(configs, isKey);
+  }
+
+  @Override
+  public Event deserialize(final String topic, final byte[] data) {
+    final int ordinal = this.byteBufferDeserializer.deserialize(topic, data).getInt();
+    for (final Event event : Event.values()) {
+      if (ordinal == event.ordinal()) {
+        return event;
+      }
+    }
+    throw new IllegalArgumentException("Deserialized data is not a valid event.");
+  }
+
+  @Override
+  public void close() {
+    this.byteBufferDeserializer.close();
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeyCoder.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeyCoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e85c3242fb854bef514787c92bb58ad76526cb4
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeyCoder.java
@@ -0,0 +1,67 @@
+package serialization;
+
+import application.SensorParentKey;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.coders.CoderException;
+import org.apache.kafka.common.serialization.Serde;
+
+/**
+ * Wrapper Class that encapsulates a SensorParentKey Serde in a org.apache.beam.sdk.coders.Coder.
+ */
+public class SensorParentKeyCoder extends Coder<SensorParentKey> implements Serializable {
+
+  private static final long serialVersionUID = -3480141901035692398L;
+  private static final boolean DETERMINISTIC = true;
+  private static final int VALUE_SIZE = 4;
+
+  private transient Serde<SensorParentKey> innerSerde = SensorParentKeySerde.serde();
+
+  @Override
+  public void encode(final SensorParentKey value, final OutputStream outStream)
+      throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = SensorParentKeySerde.serde();
+
+    }
+    final byte[] bytes = this.innerSerde.serializer().serialize("ser", value);
+    final byte[] sizeinBytes = ByteBuffer.allocate(VALUE_SIZE).putInt(bytes.length).array();
+    outStream.write(sizeinBytes);
+    outStream.write(bytes);
+
+  }
+
+  @Override
+  public SensorParentKey decode(final InputStream inStream) throws CoderException, IOException {
+    if (this.innerSerde == null) {
+      this.innerSerde = SensorParentKeySerde.serde();
+
+    }
+    final byte[] sizeinBytes = new byte[VALUE_SIZE];
+    inStream.read(sizeinBytes);
+    final int size = ByteBuffer.wrap(sizeinBytes).getInt();
+    final byte[] bytes = new byte[size];
+    inStream.read(bytes);
+    return this.innerSerde.deserializer().deserialize("deser", bytes);
+
+  }
+
+  @Override
+  public List<? extends Coder<?>> getCoderArguments() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void verifyDeterministic() throws NonDeterministicException {
+    if (!DETERMINISTIC) {
+      throw new NonDeterministicException(this, "This class should be deterministic!");
+    }
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeySerde.java b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeySerde.java
new file mode 100644
index 0000000000000000000000000000000000000000..468adb3947439c11c4fd9b289f41b68e606bdb1d
--- /dev/null
+++ b/theodolite-benchmarks/uc4-beam/src/main/java/serialization/SensorParentKeySerde.java
@@ -0,0 +1,34 @@
+package serialization;
+
+import application.SensorParentKey;
+import org.apache.kafka.common.serialization.Serde;
+import titan.ccp.common.kafka.simpleserdes.BufferSerde;
+import titan.ccp.common.kafka.simpleserdes.ReadBuffer;
+import titan.ccp.common.kafka.simpleserdes.SimpleSerdes;
+import titan.ccp.common.kafka.simpleserdes.WriteBuffer;
+
+/**
+ * {@link Serde} factory for {@link SensorParentKey}.
+ */
+public final class SensorParentKeySerde implements BufferSerde<SensorParentKey> {
+
+  private SensorParentKeySerde() {}
+
+  @Override
+  public void serialize(final WriteBuffer buffer, final SensorParentKey key) {
+    buffer.putString(key.getSensor());
+    buffer.putString(key.getParent());
+  }
+
+  @Override
+  public SensorParentKey deserialize(final ReadBuffer buffer) {
+    final String sensor = buffer.getString();
+    final String parent = buffer.getString();
+    return new SensorParentKey(sensor, parent);
+  }
+
+  public static Serde<SensorParentKey> serde() {
+    return SimpleSerdes.create(new SensorParentKeySerde());
+  }
+
+}
diff --git a/theodolite-benchmarks/uc4-flink/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc4-flink/.settings/org.eclipse.jdt.ui.prefs
index 272e01533f6a345d53d2635c47e38c6d3c33dc8a..08fcb07933ca19165976bffd5e7fdfdaf64ee1d2 100644
--- a/theodolite-benchmarks/uc4-flink/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc4-flink/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc4-kstreams/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc4-kstreams/.settings/org.eclipse.jdt.ui.prefs
index fa98ca63d77bdee891150bd6713f70197a75cefc..a375cb792eeb842ecfd1f789fbf6a716df43e9c8 100644
--- a/theodolite-benchmarks/uc4-kstreams/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc4-kstreams/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite-benchmarks/uc4-load-generator/.settings/org.eclipse.jdt.ui.prefs b/theodolite-benchmarks/uc4-load-generator/.settings/org.eclipse.jdt.ui.prefs
index 4d01df75552c562406705858b6368ecf59d6e82f..ac23341bf71ac68df4183361493261758fd5dafb 100644
--- a/theodolite-benchmarks/uc4-load-generator/.settings/org.eclipse.jdt.ui.prefs
+++ b/theodolite-benchmarks/uc4-load-generator/.settings/org.eclipse.jdt.ui.prefs
@@ -61,7 +61,7 @@ cleanup_settings_version=2
 eclipse.preferences.version=1
 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
 formatter_profile=_CAU-SE-Style
-formatter_settings_version=15
+formatter_settings_version=21
 org.eclipse.jdt.ui.ignorelowercasenames=true
 org.eclipse.jdt.ui.importorder=;
 org.eclipse.jdt.ui.ondemandthreshold=99
diff --git a/theodolite/.gitignore b/theodolite/.gitignore
index a1eff0e1d4dddacdbcafa2c235b28616cb53e7bf..285b6baee527835a20f0b79f1ecece49b80f7d42 100644
--- a/theodolite/.gitignore
+++ b/theodolite/.gitignore
@@ -31,3 +31,6 @@ nb-configuration.xml
 # patch
 *.orig
 *.rej
+
+# Local environment
+.env
diff --git a/theodolite/README.md b/theodolite/README.md
index 521f6aeba12e5d6bc17fb80ef0f2a525bbe20675..f662329f7eda3a39632581b7125a2f2f2feced8a 100644
--- a/theodolite/README.md
+++ b/theodolite/README.md
@@ -1,25 +1,19 @@
-# Theodolite project
+# Theodolite
 
 This project uses Quarkus, the Supersonic Subatomic Java Framework.
 
-If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/> .
+If you want to learn more about Quarkus, please visit its website: https://quarkus.io/.
 
 ## Running the application in dev mode
 
-You can run your application in dev mode using:
+You can run your application in dev mode that enables live coding using:
 
 ```sh
 ./gradlew quarkusDev
 ```
 
-### Hint for running with k3s (or k3d)
+> **_NOTE:_**  Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
 
-You may need to add the following dependencies to the `build.gradle` file when running Theodolite with k3s.
-
-```
-implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.68'
-implementation 'org.bouncycastle:bcpkix-jdk15on:1.68'
-```
 
 ## Packaging and running the application
 
@@ -29,8 +23,10 @@ The application can be packaged using:
 ./gradlew build
 ```
 
-It produces the `theodolite-0.6.0-SNAPSHOT-runner.jar` file in the `/build` directory. Be aware that it’s not
-an _über-jar_ as the dependencies are copied into the `build/lib` directory.
+It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
+Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
+
+The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
 
 If you want to build an _über-jar_, execute the following command:
 
@@ -38,12 +34,10 @@ If you want to build an _über-jar_, execute the following command:
 ./gradlew build -Dquarkus.package.type=uber-jar
 ```
 
-The application is now runnable using `java -jar build/theodolite-0.6.0-SNAPSHOT-runner.jar`.
+The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
 
 ## Creating a native executable
 
-It is recommended to use the native GraalVM images to create executable jars from Theodolite. For more information please visit the [Native Image guide](https://www.graalvm.org/reference-manual/native-image/).
-
 You can create a native executable using:
 
 ```sh
@@ -57,19 +51,25 @@ Or, if you don't have GraalVM installed, you can run the native executable build
 ```
 
 You can then execute your native executable with:
-```./build/theodolite-0.6.0-SNAPSHOT-runner```
+```./build/theodolite-0.7.0-SNAPSHOT-runner```
 
-If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
+If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.
 
-## Build docker images
+## Building container images
 
-For the jvm version use:
+For the JVM version use:
 
 ```sh
 ./gradlew build
 docker build -f src/main/docker/Dockerfile.jvm -t theodolite-jvm .
 ```
 
+Alternatively, you can also use Kaniko to build the image:
+
+```sh
+docker run -it --rm --name kaniko -v "`pwd`":/theodolite --entrypoint "" gcr.io/kaniko-project/executor:debug /kaniko/executor --context /theodolite --dockerfile src/main/docker/Dockerfile.jvm --no-push
+```
+
 For the native image version use:
 
 ```sh
@@ -77,7 +77,7 @@ For the native image version use:
 docker build -f src/main/docker/Dockerfile.native -t theodolite-native .
 ```
 
-## Execute docker images
+## Run a container
 
 Remember to set the environment variables first.
 
diff --git a/theodolite/build.gradle b/theodolite/build.gradle
index 39cc62804a382888f8d0019e639c5ff4febe8d59..a066e94f09b71720f9392947640b077b153ccb9c 100644
--- a/theodolite/build.gradle
+++ b/theodolite/build.gradle
@@ -1,14 +1,14 @@
 plugins {
-    id 'org.jetbrains.kotlin.jvm' version "1.3.72"
-    id "org.jetbrains.kotlin.plugin.allopen" version "1.3.72"
+    id 'org.jetbrains.kotlin.jvm' version "1.6.10"
+    id "org.jetbrains.kotlin.plugin.allopen" version "1.6.10"
     id 'io.quarkus'
-    id "io.gitlab.arturbosch.detekt" version "1.15.0"   //For code style
-    id "org.jlleitschuh.gradle.ktlint" version "10.0.0" // same as above
+    id "io.gitlab.arturbosch.detekt" version "1.15.0"
+    id "org.jlleitschuh.gradle.ktlint" version "10.0.0"
 }
 
 repositories {
-    mavenLocal()
     mavenCentral()
+    mavenLocal()
     jcenter()
 }
 
@@ -18,25 +18,26 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
     implementation 'io.quarkus:quarkus-arc'
     implementation 'io.quarkus:quarkus-resteasy'
-    implementation 'com.google.code.gson:gson:2.8.5'
-    implementation 'org.slf4j:slf4j-simple:1.7.29'
-    implementation 'io.github.microutils:kotlin-logging:1.12.0'
-    implementation('io.fabric8:kubernetes-client:5.4.1'){force = true}
-    implementation('io.fabric8:kubernetes-model-core:5.4.1'){force = true}
-    implementation('io.fabric8:kubernetes-model-common:5.4.1'){force = true}
-    implementation 'org.apache.kafka:kafka-clients:2.7.0'
-    implementation 'khttp:khttp:1.0.0'
+    implementation 'io.quarkus:quarkus-kubernetes-client'
 
-    compile 'junit:junit:4.12'
+    implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.69'
+    implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
+
+    implementation 'com.google.code.gson:gson:2.8.9'
+    implementation 'org.slf4j:slf4j-simple:1.7.32'
+    implementation 'io.github.microutils:kotlin-logging:2.1.16'
+    implementation 'org.apache.kafka:kafka-clients:2.8.0'
+    implementation 'khttp:khttp:1.0.0'
 
     testImplementation 'io.quarkus:quarkus-junit5'
+    testImplementation 'io.quarkus:quarkus-test-kubernetes-client'
     testImplementation 'io.rest-assured:rest-assured'
-    testImplementation 'org.junit-pioneer:junit-pioneer:1.4.0'
-    testImplementation ('io.fabric8:kubernetes-server-mock:5.4.1'){force = true}
+    testImplementation 'org.junit-pioneer:junit-pioneer:1.5.0'
+    testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
 }
 
 group 'theodolite'
-version '0.6.0-SNAPSHOT'
+version '0.7.0-SNAPSHOT'
 
 java {
     sourceCompatibility = JavaVersion.VERSION_11
@@ -57,6 +58,7 @@ compileKotlin {
 compileTestKotlin {
     kotlinOptions.jvmTarget = JavaVersion.VERSION_11
 }
+
 detekt {
     failFast = true // fail build on any finding
     buildUponDefaultConfig = true
diff --git a/theodolite/build_jvm.sh b/theodolite/build_jvm.sh
deleted file mode 100755
index f4dd32fc5228576f09e95f0e8ac06fa08ea6acc7..0000000000000000000000000000000000000000
--- a/theodolite/build_jvm.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-
-./gradlew build -x test
-
-docker build -f src/main/docker/Dockerfile.jvm -t quarkus/theodolite-jvm .
-
-docker run -i --rm -p 8080:8080 quarkus/theodolite-jvm
diff --git a/theodolite/build_native.sh b/theodolite/build_native.sh
deleted file mode 100755
index c2d7d81f35a24af951005bb30c52a8ab494ddb64..0000000000000000000000000000000000000000
--- a/theodolite/build_native.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-
-./gradlew build -Dquarkus.package.type=native -x test
-
-docker build -f src/main/docker/Dockerfile.native -t quarkus/theodolite .
-
-docker run -i --rm -p 8080:8080 quarkus/theodolite
diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml
index 7ab2e5f3b890a883f68dbbd36805f3791158f256..c901e61360c05b2f1cf2b1767a20f624eb262231 100644
--- a/theodolite/crd/crd-benchmark.yaml
+++ b/theodolite/crd/crd-benchmark.yaml
@@ -20,7 +20,7 @@ spec:
         properties:
           spec:
             type: object
-            required: ["sut", "loadGenerator", "resourceTypes", "loadTypes", "kafkaConfig"]
+            required: ["sut", "loadGenerator", "resourceTypes", "loadTypes"]
             properties:
               name:
                 description: This field exists only for technical reasons and should not be set by the user. The value of the field will be overwritten.
@@ -64,13 +64,87 @@ spec:
                               type: array
                               items:
                                 type: string
+                  beforeActions:
+                    type: array
+                    default: []
+                    description: Infrastructure before actions are executed before the infrastructure is set up.
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
+                  afterActions:
+                    type: array
+                    default: []
+                    description: Infrastructure after actions are executed after the teardown of the infrastructure.
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
               sut:
                 description: The appResourceSets specifies all Kubernetes resources required to start the sut. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
                 type: object
                 properties:
                   resources:
                     type: array
-                    default: [ ]
+                    default: []
                     items:
                       type: object
                       oneOf:
@@ -101,6 +175,79 @@ spec:
                               type: array
                               items:
                                 type: string
+                  beforeActions:
+                    type: array
+                    default: []
+                    description: SUT before actions are executed before the SUT is started.
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
+                  afterActions:
+                    type: array
+                    default: []
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
               loadGenerator:
                 description: The loadGenResourceSets specifies all Kubernetes resources required to start the load generator. A resourceSet can be either a configMap resourceSet or a fileSystem resourceSet.
                 type: object
@@ -138,6 +285,80 @@ spec:
                               type: array
                               items:
                                 type: string
+                  beforeActions:
+                    type: array
+                    default: [ ]
+                    description: Load generator before actions are executed before the load generator is started.
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
+                  afterActions:
+                    type: array
+                    default: []
+                    description: Load generator after actions are executed after the teardown of the load generator.
+                    items:
+                      type: object
+                      properties:
+                        selector:
+                          type: object
+                          description: The selector specifies which resource should be selected for the execution of the command.
+                          properties:
+                            pod:
+                              type: object
+                              description: Specifies the pod.
+                              properties:
+                                matchLabels:
+                                  type: object
+                                  description: The matchLabels of the desired pod.
+                                  additionalProperties: true
+                                  x-kubernetes-map-type: "granular"
+                                  default: { }
+                            container:
+                              description: Specifies the container.
+                              default: ""
+                              type: string
+                        exec:
+                          type: object
+                          description: Specifies command to be executed.
+                          properties:
+                            command:
+                              type: array
+                              description: The command to be executed as string array.
+                              items:
+                                type: string
+                            timeoutSeconds:
+                              description: Specifies the timeout (in seconds) for the specified command.
+                              type: integer
               resourceTypes:
                 description: A list of resource types that can be scaled for this `benchmark` resource. For each resource type the concrete values are defined in the `execution` object.
                 type: array
@@ -246,7 +467,7 @@ spec:
     - name: Age
       type: date
       jsonPath: .metadata.creationTimestamp
-    - name: STATUS
+    - name: Status
       type: string
       description: The status of a Benchmark indicates whether all resources are available to start the benchmark or not.
       jsonPath: .status.resourceSetsState
diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml
index d9cd41903bb2fdc18bd6640bdbe2eb764b2106ab..92a8ca18d87009143620097caf2abfe8da202c82 100644
--- a/theodolite/crd/crd-execution.yaml
+++ b/theodolite/crd/crd-execution.yaml
@@ -133,10 +133,18 @@ spec:
                 description: ""
                 type: string
               executionDuration:
-                description: "Duration of the execution in seconds"
+                description: "Duration of the execution"
                 type: string
+              startTime:
+                description: "Time this execution started"
+                type: string
+                format: date-time
+              completionTime:
+                description: "Time when this execution was stopped"
+                type: string
+                format: date-time
     additionalPrinterColumns:
-    - name: STATUS
+    - name: Status
       type: string
       description: State of the execution
       jsonPath: .status.executionState
diff --git a/theodolite/gradle.properties b/theodolite/gradle.properties
index d7e4187c25e76dfb440650274b2d383f75a32242..fd5768bc24a65dbd43b3ea770c854ae7c0da0a91 100644
--- a/theodolite/gradle.properties
+++ b/theodolite/gradle.properties
@@ -1,8 +1,8 @@
 #Gradle properties
 quarkusPluginId=io.quarkus
-quarkusPluginVersion=1.10.3.Final
-quarkusPlatformGroupId=io.quarkus
-quarkusPlatformArtifactId=quarkus-universe-bom
-quarkusPlatformVersion=1.10.3.Final
+quarkusPluginVersion=2.6.3.Final
+quarkusPlatformGroupId=io.quarkus.platform
+quarkusPlatformArtifactId=quarkus-bom
+quarkusPlatformVersion=2.6.3.Final
 
-org.gradle.logging.level=INFO
\ No newline at end of file
+#org.gradle.logging.level=INFO
\ No newline at end of file
diff --git a/theodolite/gradle/wrapper/gradle-wrapper.properties b/theodolite/gradle/wrapper/gradle-wrapper.properties
index bb8b2fc26b2e572c79d7212a4f6f11057c6787f7..e750102e09269a4ac558e10a6612998e5ca4c0f2 100644
--- a/theodolite/gradle/wrapper/gradle-wrapper.properties
+++ b/theodolite/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/theodolite/gradlew.bat b/theodolite/gradlew.bat
old mode 100755
new mode 100644
diff --git a/theodolite/src/main/docker/Dockerfile.fast-jar b/theodolite/src/main/docker/Dockerfile.fast-jar
deleted file mode 100644
index 16853dd8f064565ae017bee9dae3597b63085006..0000000000000000000000000000000000000000
--- a/theodolite/src/main/docker/Dockerfile.fast-jar
+++ /dev/null
@@ -1,54 +0,0 @@
-####
-# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
-#
-# Before building the container image run:
-#
-# ./gradlew build -Dquarkus.package.type=fast-jar
-#
-# Then, build the image with:
-#
-# docker build -f src/main/docker/Dockerfile.fast-jar -t quarkus/theodolite-fast-jar .
-#
-# Then run the container using:
-#
-# docker run -i --rm -p 8080:8080 quarkus/theodolite-fast-jar
-#
-# If you want to include the debug port into your docker image
-# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5050
-#
-# Then run the container using :
-#
-# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-fast-jar
-#
-###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 
-
-ARG JAVA_PACKAGE=java-11-openjdk-headless
-ARG RUN_JAVA_VERSION=1.3.8
-ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
-# Install java and the run-java script
-# Also set up permissions for user `1001`
-RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
-    && microdnf update \
-    && microdnf clean all \
-    && mkdir /deployments \
-    && chown 1001 /deployments \
-    && chmod "g+rwX" /deployments \
-    && chown 1001:root /deployments \
-    && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
-    && chown 1001 /deployments/run-java.sh \
-    && chmod 540 /deployments/run-java.sh \
-    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
-
-# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
-ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
-# We make four distinct layers so if there are application changes the library layers can be re-used
-COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/
-COPY --chown=1001 build/quarkus-app/*.jar /deployments/
-COPY --chown=1001 build/quarkus-app/app/ /deployments/app/
-COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/
-
-EXPOSE 8080
-USER 1001
-
-ENTRYPOINT [ "/deployments/run-java.sh" ]
diff --git a/theodolite/src/main/docker/Dockerfile.jvm b/theodolite/src/main/docker/Dockerfile.jvm
index 4d51240e0225bb571cc4a625e40c9ec76fd8f10d..e33d7c379a4336610c16d59b9d3315a1e8abad2b 100644
--- a/theodolite/src/main/docker/Dockerfile.jvm
+++ b/theodolite/src/main/docker/Dockerfile.jvm
@@ -14,38 +14,28 @@
 # docker run -i --rm -p 8080:8080 quarkus/theodolite-jvm
 #
 # If you want to include the debug port into your docker image
-# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5050
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
 #
 # Then run the container using :
 #
-# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-jvm
+# docker run -i --rm -p 8080:8080 quarkus/theodolite-jvm
 #
 ###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 
+FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.10
 
-ARG JAVA_PACKAGE=java-11-openjdk-headless
-ARG RUN_JAVA_VERSION=1.3.8
 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
-# Install java and the run-java script
-# Also set up permissions for user `1001`
-RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
-    && microdnf update \
-    && microdnf clean all \
-    && mkdir /deployments \
-    && chown 1001 /deployments \
-    && chmod "g+rwX" /deployments \
-    && chown 1001:root /deployments \
-    && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
-    && chown 1001 /deployments/run-java.sh \
-    && chmod 540 /deployments/run-java.sh \
-    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
 
 # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
 ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
-COPY build/lib/* /deployments/lib/
-COPY build/*-runner.jar /deployments/app.jar
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 build/quarkus-app/*.jar /deployments/
+COPY --chown=185 build/quarkus-app/app/ /deployments/app/
+COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
 
 EXPOSE 8080
-USER 1001
+USER 185
+
+ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
 
-ENTRYPOINT [ "/deployments/run-java.sh" ]
diff --git a/theodolite/src/main/docker/Dockerfile.legacy-jar b/theodolite/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 0000000000000000000000000000000000000000..aa5908c4ed42f005fa67c17fd2c3b3e00978228a
--- /dev/null
+++ b/theodolite/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,37 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.package.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/theodolite-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/theodolite-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/theodolite-legacy-jar
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.10
+
+ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
+
+# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+
+COPY build/lib/* /deployments/lib/
+COPY build/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+
+ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
diff --git a/theodolite/src/main/docker/Dockerfile.native b/theodolite/src/main/docker/Dockerfile.native
index 95ef4fb51d7dc1ac520fb4c5a9af1b2d0a32fd09..34ccd6622bf2fba6f9707989fffd9bb6390a4a8b 100644
--- a/theodolite/src/main/docker/Dockerfile.native
+++ b/theodolite/src/main/docker/Dockerfile.native
@@ -14,12 +14,12 @@
 # docker run -i --rm -p 8080:8080 quarkus/theodolite
 #
 ###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
-WORKDIR /deployments
-RUN chown 1001 /deployments \
-    && chmod "g+rwX" /deployments \
-    && chown 1001:root /deployments
-COPY --chown=1001:root build/*-runner /deployments/application
+FROM quay.io/quarkus/quarkus-micro-image:1.0
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root build/*-runner /work/application
 
 EXPOSE 8080
 USER 1001
diff --git a/theodolite/src/main/docker/Dockerfile.native-distroless b/theodolite/src/main/docker/Dockerfile.native-distroless
new file mode 100644
index 0000000000000000000000000000000000000000..951dfb64bee56e277d057c8f9e97796e88f30ac2
--- /dev/null
+++ b/theodolite/src/main/docker/Dockerfile.native-distroless
@@ -0,0 +1,23 @@
+####
+# This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.package.type=native
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/theodolite .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/theodolite
+#
+###
+FROM quay.io/quarkus/quarkus-distroless-image:1.0
+COPY build/*-runner /application
+
+EXPOSE 8080
+USER nonroot
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Action.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Action.kt
new file mode 100644
index 0000000000000000000000000000000000000000..35efebdc0fb2a3748660cb76cdd5499b4ca5f622
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/Action.kt
@@ -0,0 +1,48 @@
+package theodolite.benchmark
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient
+import io.quarkus.runtime.annotations.RegisterForReflection
+import theodolite.util.ActionCommandFailedException
+import theodolite.util.Configuration
+
+@JsonDeserialize
+@RegisterForReflection
+@JsonInclude(JsonInclude.Include.NON_NULL)
+class Action {
+
+    lateinit var selector: ActionSelector
+    lateinit var exec: Command
+
+    fun exec(client: NamespacedKubernetesClient) {
+        val exitCode = ActionCommand(client = client)
+            .exec(
+                matchLabels = selector.pod.matchLabels,
+                container = selector.container,
+                timeout = exec.timeoutSeconds,
+                command = exec.command
+        )
+            if(exitCode != 0){
+            throw ActionCommandFailedException("Error while executing action, finished with exit code $exitCode")
+        }
+    }
+}
+
+@JsonDeserialize
+@RegisterForReflection
+class ActionSelector {
+    lateinit var pod: PodSelector
+    var container: String = ""
+}
+@JsonDeserialize
+@RegisterForReflection
+class PodSelector {
+    lateinit var matchLabels: MutableMap<String, String>
+}
+@JsonDeserialize
+@RegisterForReflection
+class Command {
+    lateinit var command: Array<String>
+    var timeoutSeconds: Long = Configuration.TIMEOUT_SECONDS
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt
new file mode 100644
index 0000000000000000000000000000000000000000..966fa56329c8d7d466dd14858bcbc06bb5b857c3
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt
@@ -0,0 +1,161 @@
+package theodolite.benchmark
+
+import io.fabric8.kubernetes.api.model.Status
+import io.fabric8.kubernetes.client.KubernetesClientException
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient
+import io.fabric8.kubernetes.client.dsl.ExecListener
+import io.fabric8.kubernetes.client.dsl.ExecWatch
+import io.fabric8.kubernetes.client.utils.Serialization
+import mu.KotlinLogging
+import okhttp3.Response
+import theodolite.util.ActionCommandFailedException
+import theodolite.util.Configuration
+import java.io.ByteArrayOutputStream
+import java.time.Duration
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+
+private val logger = KotlinLogging.logger {}
+
+class ActionCommand(val client: NamespacedKubernetesClient) {
+    var out: ByteArrayOutputStream = ByteArrayOutputStream()
+    var error: ByteArrayOutputStream = ByteArrayOutputStream()
+    var errChannelStream: ByteArrayOutputStream = ByteArrayOutputStream()
+    private val execLatch = CountDownLatch(1)
+
+    /**
+     * Executes an action command.
+     *
+     * @param matchLabels matchLabels specifies on which pod the command should be executed. For this, the principle
+     * `of any` of is used and the command is called on one of the possible pods.
+     * @param container (Optional) The container to run the command. Is optional iff exactly one container exist.
+     * @param command The command to be executed.
+     * @return the exit code of this executed command
+     */
+    fun exec(
+        matchLabels: MutableMap<String, String>,
+        command: Array<String>,
+        timeout: Long = Configuration.TIMEOUT_SECONDS,
+        container: String = ""
+    ): Int {
+        try {
+            val execWatch: ExecWatch = if (container.isNotEmpty()) {
+                client.pods()
+                    .inNamespace(client.namespace)
+                    .withName(getPodName(matchLabels, 3))
+                    .inContainer(container)
+
+            } else {
+                client.pods()
+                    .inNamespace(client.namespace)
+                    .withName(getPodName(matchLabels, 3))
+            }
+                .writingOutput(out)
+                .writingError(error)
+                .writingErrorChannel(errChannelStream)
+                .usingListener(ActionCommandListener(execLatch))
+                .exec(*command)
+
+            val latchTerminationStatus = execLatch.await(timeout, TimeUnit.SECONDS)
+            if (!latchTerminationStatus) {
+                throw ActionCommandFailedException("Latch could not terminate within specified time")
+            }
+            execWatch.close()
+        } catch (e: Exception) {
+            when (e) {
+                is InterruptedException -> {
+                    Thread.currentThread().interrupt()
+                    throw ActionCommandFailedException("Interrupted while waiting for the exec", e)
+                }
+                is KubernetesClientException -> {
+                    throw ActionCommandFailedException("Error while executing command", e)
+                }
+                else -> {
+                    throw e
+                }
+            }
+        }
+        logger.debug { "Execution Output Stream is \n $out" }
+        logger.debug { "Execution Error Stream is \n $error" }
+        logger.debug { "Execution ErrorChannel is: \n $errChannelStream" }
+        return getExitCode(errChannelStream)
+    }
+
+    private fun getExitCode(errChannelStream: ByteArrayOutputStream): Int {
+        val status: Status?
+        try {
+            status = Serialization.unmarshal(errChannelStream.toString(), Status::class.java)
+        } catch (e: Exception) {
+            throw ActionCommandFailedException("Could not determine the exit code, no information given")
+        }
+
+        if (status == null) {
+            throw ActionCommandFailedException("Could not determine the exit code, no information given")
+        }
+
+        return if (status.status.equals("Success")) {
+            0
+        } else status.details.causes.stream()
+            .filter { it.reason.equals("ExitCode") }
+            .map { it.message }
+            .findFirst()
+            .orElseThrow {
+                ActionCommandFailedException("Status is not SUCCESS but contains no exit code - Status: $status")
+            }.toInt()
+    }
+
+    /**
+     * Find pod with matching labels. The matching pod must have the status `Running`.
+     *
+     * @param matchLabels the match labels
+     * @param tries specifies the number of times to look for a  matching pod. When pods are newly created,
+     * it can take a while until the status is ready and the pod can be selected.
+     * @return the name of the pod or throws [ActionCommandFailedException]
+     */
+    fun getPodName(matchLabels: MutableMap<String, String>, tries: Int): String {
+        for (i in 1..tries) {
+
+            try {
+                return getPodName(matchLabels)
+            } catch (e: Exception) {
+                logger.warn { "Could not found any pod with specified matchlabels or pod is not ready." }
+            }
+            Thread.sleep(Duration.ofSeconds(5).toMillis())
+        }
+        throw ActionCommandFailedException("Couldn't find any pod that matches the specified labels.")
+    }
+
+    private fun getPodName(matchLabels: MutableMap<String, String>): String {
+        return try {
+            val podNames = this.client
+                .pods()
+                .withLabels(matchLabels)
+                .list()
+                .items
+                .map { it.metadata.name }
+
+            podNames.first {
+                this.client.pods().withName(it).isReady
+            }
+
+        } catch (e: NoSuchElementException) {
+            throw ActionCommandFailedException("Couldn't find any pod that matches the specified labels.", e)
+        }
+    }
+
+    private class ActionCommandListener(val execLatch: CountDownLatch) : ExecListener {
+        override fun onOpen(response: Response) {
+        }
+
+        override fun onFailure(throwable: Throwable, response: Response) {
+            execLatch.countDown()
+            throw ActionCommandFailedException("Some error encountered while executing action, caused ${throwable.message})")
+        }
+
+        override fun onClose(code: Int, reason: String) {
+            execLatch.countDown()
+        }
+    }
+
+}
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
index 273a13170e77ae9e2f5f09869ebbc5cc06185715..f85b83497e5d69e43c1d4784ef86170a5436e929 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
@@ -5,59 +5,56 @@ import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.KubernetesClientException
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromString
 import theodolite.util.DeploymentFailedException
 import theodolite.util.YamlParserFromString
 import java.lang.IllegalArgumentException
-import java.lang.IllegalStateException
-
-private val logger = KotlinLogging.logger {}
 
 @RegisterForReflection
 @JsonDeserialize
-class ConfigMapResourceSet: ResourceSet, KubernetesResource {
+class ConfigMapResourceSet : ResourceSet, KubernetesResource {
     lateinit var name: String
     lateinit var files: List<String> // load all files, iff files is not set
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun getResourceSet(client: NamespacedKubernetesClient): Collection<Pair<String, KubernetesResource>> {
         val loader = K8sResourceLoaderFromString(client)
         var resources: Map<String, String>
 
         try {
-            resources = client
+            resources = (client
                 .configMaps()
                 .withName(name)
-                .get()
+                .get() ?: throw DeploymentFailedException("Cannot find ConfigMap with name '$name'."))
                 .data
-                .filter { it.key.endsWith(".yaml") } // consider only yaml files, e.g. ignore readme files
+                .filter { it.key.endsWith(".yaml") || it.key.endsWith(".yml")}
         } catch (e: KubernetesClientException) {
-            throw DeploymentFailedException("can not find or read configmap:  $name", e)
-        } catch (e: IllegalStateException) {
-            throw DeploymentFailedException("can not find configmap or data section is null $name", e)
+            throw DeploymentFailedException("Cannot find or read ConfigMap with name '$name'.", e)
         }
 
-        if (::files.isInitialized){
-            resources = resources
-                .filter { files.contains(it.key) }
-
-            if (resources.size != files.size) {
-                throw  DeploymentFailedException("Could not find all specified Kubernetes manifests files")
+        if (::files.isInitialized) {
+            val filteredResources = resources.filter { files.contains(it.key) }
+            if (filteredResources.size != files.size) {
+                throw DeploymentFailedException("Could not find all specified Kubernetes manifests files")
             }
+            resources = filteredResources
         }
 
         return try {
             resources
-                .map { Pair(
-                    getKind(resource = it.value),
-                    it) }
+                .map {
+                    Pair(
+                        getKind(resource = it.value),
+                        it
+                    )
+                }
                 .map {
                     Pair(
                         it.second.key,
-                        loader.loadK8sResource(it.first, it.second.value)) }
+                        loader.loadK8sResource(it.first, it.second.value)
+                    )
+                }
         } catch (e: IllegalArgumentException) {
-            throw  DeploymentFailedException("Can not creat resource set from specified configmap", e)
+            throw DeploymentFailedException("Cannot create resource set from specified ConfigMap", e)
         }
 
     }
@@ -66,10 +63,7 @@ class ConfigMapResourceSet: ResourceSet, KubernetesResource {
         val parser = YamlParserFromString()
         val resourceAsMap = parser.parse(resource, HashMap<String, String>()::class.java)
 
-        return try {
-            resourceAsMap?.get("kind") !!
-        } catch (e: NullPointerException) {
-            throw DeploymentFailedException( "Could not find field kind of Kubernetes resource: ${resourceAsMap?.get("name")}", e)
-        }
+        return resourceAsMap?.get("kind")
+            ?: throw DeploymentFailedException("Could not find field kind of Kubernetes resource: ${resourceAsMap?.get("name")}")
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
index 92df1bec3cd6f21b1f830e73b466f70e37a9f4c8..f830232de4b6956fa0f989cae131903377862e6c 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
@@ -2,10 +2,8 @@ package theodolite.benchmark
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
-import io.fabric8.kubernetes.client.DefaultKubernetesClient
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.util.DeploymentFailedException
 import theodolite.util.YamlParserFromFile
@@ -13,8 +11,6 @@ import java.io.File
 import java.io.FileNotFoundException
 import java.lang.IllegalArgumentException
 
-private val logger = KotlinLogging.logger {}
-
 @RegisterForReflection
 @JsonDeserialize
 class FileSystemResourceSet: ResourceSet, KubernetesResource {
@@ -32,7 +28,7 @@ class FileSystemResourceSet: ResourceSet, KubernetesResource {
         return try {
             File(path)
                 .list() !!
-                .filter { it.endsWith(".yaml") } // consider only yaml files, e.g. ignore readme files
+                .filter { it.endsWith(".yaml") || it.endsWith(".yml") }
                 .map {
                     loadSingleResource(resourceURL = it, client = client)
                 }
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
index 0b81f8701f92a95662efef6e0d58839c9a2f6f3b..d42c2ea3c0ed5394fdcf5b89be0fe0470a15ba62 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
@@ -1,6 +1,5 @@
 package theodolite.benchmark
 
-import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.DefaultKubernetesClient
@@ -40,14 +39,14 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
     lateinit var name: String
     lateinit var resourceTypes: List<TypeName>
     lateinit var loadTypes: List<TypeName>
-    lateinit var kafkaConfig: KafkaConfig
+    var kafkaConfig: KafkaConfig? = null
     lateinit var infrastructure: Resources
     lateinit var sut: Resources
     lateinit var loadGenerator: Resources
-    var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
+    private var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
 
     @Transient
-    private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace)
+    private var client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace)
 
     /**
      * Loads [KubernetesResource]s.
@@ -59,6 +58,7 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
     }
 
     override fun setupInfrastructure() {
+        this.infrastructure.beforeActions.forEach { it.exec(client = client) }
         val kubernetesManager = K8sManager(this.client)
         loadKubernetesResources(this.infrastructure.resources)
             .map{it.second}
@@ -70,7 +70,8 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
         loadKubernetesResources(this.infrastructure.resources)
             .map{it.second}
             .forEach { kubernetesManager.remove(it) }
-        }
+        this.infrastructure.afterActions.forEach { it.exec(client = client) }
+    }
 
     /**
      * Builds a deployment.
@@ -109,14 +110,30 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
                 patcherFactory.createPatcher(it.patcher, appResources + loadGenResources).patch(override.value)
             }
         }
+
+        val kafkaConfig = this.kafkaConfig
+
         return KubernetesBenchmarkDeployment(
+            sutBeforeActions = sut.beforeActions,
+            sutAfterActions = sut.afterActions,
+            loadGenBeforeActions = loadGenerator.beforeActions,
+            loadGenAfterActions = loadGenerator.afterActions,
             appResources = appResources.map { it.second },
             loadGenResources = loadGenResources.map { it.second },
             loadGenerationDelay = loadGenerationDelay,
             afterTeardownDelay = afterTeardownDelay,
-            kafkaConfig = hashMapOf("bootstrap.servers" to kafkaConfig.bootstrapServer),
-            topics = kafkaConfig.topics,
+            kafkaConfig = if (kafkaConfig != null) hashMapOf("bootstrap.servers" to kafkaConfig.bootstrapServer) else mapOf(),
+            topics = kafkaConfig?.topics ?: listOf(),
             client = this.client
         )
     }
+
+    /**
+     * This function can be used to set the Kubernetes client manually. This is for example necessary for testing.
+     *
+     * @param client
+     */
+    fun setClient(client: NamespacedKubernetesClient) {
+        this.client = client
+    }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt
index 423ac92c654ff55057796d9642c2cb408bc62fe5..3331444a17b4c2a1aa4411c1e27b3d1e087f8841 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt
@@ -23,11 +23,15 @@ private val logger = KotlinLogging.logger {}
  */
 @RegisterForReflection
 class KubernetesBenchmarkDeployment(
+    private val sutBeforeActions: List<Action>,
+    private val sutAfterActions: List<Action>,
+    private val loadGenBeforeActions: List<Action>,
+    private val loadGenAfterActions: List<Action>,
     val appResources: List<KubernetesResource>,
     val loadGenResources: List<KubernetesResource>,
     private val loadGenerationDelay: Long,
     private val afterTeardownDelay: Long,
-    private val kafkaConfig: HashMap<String, Any>,
+    private val kafkaConfig: Map<String, Any>,
     private val topics: List<KafkaConfig.TopicWrapper>,
     private val client: NamespacedKubernetesClient
 ) : BenchmarkDeployment {
@@ -42,13 +46,19 @@ class KubernetesBenchmarkDeployment(
      *  - Deploy the needed resources.
      */
     override fun setup() {
-        val kafkaTopics = this.topics.filter { !it.removeOnly }
-            .map { NewTopic(it.name, it.numPartitions, it.replicationFactor) }
-        kafkaController.createTopics(kafkaTopics)
+        if (this.topics.isNotEmpty()) {
+            val kafkaTopics = this.topics
+                .filter { !it.removeOnly }
+                .map { NewTopic(it.name, it.numPartitions, it.replicationFactor) }
+            kafkaController.createTopics(kafkaTopics)
+        }
+        sutBeforeActions.forEach { it.exec(client = client) }
         appResources.forEach { kubernetesManager.deploy(it) }
         logger.info { "Wait ${this.loadGenerationDelay} seconds before starting the load generator." }
         Thread.sleep(Duration.ofSeconds(this.loadGenerationDelay).toMillis())
+        loadGenBeforeActions.forEach { it.exec(client = client) }
         loadGenResources.forEach { kubernetesManager.deploy(it) }
+
     }
 
     /**
@@ -59,8 +69,12 @@ class KubernetesBenchmarkDeployment(
      */
     override fun teardown() {
         loadGenResources.forEach { kubernetesManager.remove(it) }
+        loadGenAfterActions.forEach { it.exec(client = client) }
         appResources.forEach { kubernetesManager.remove(it) }
-        kafkaController.removeTopics(this.topics.map { topic -> topic.name })
+        sutAfterActions.forEach { it.exec(client = client) }
+        if (this.topics.isNotEmpty()) {
+            kafkaController.removeTopics(this.topics.map { topic -> topic.name })
+        }
         ResourceByLabelHandler(client).removePods(
             labelName = LAG_EXPORTER_POD_LABEL_NAME,
             labelValue = LAG_EXPORTER_POD_LABEL_VALUE
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
index a4fe443e7f304c411792ee06c32592ba3c9e692a..b6364949727d4ea134e348ce8b79e22334753c1c 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
@@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.util.DeploymentFailedException
 
 @JsonDeserialize
@@ -14,7 +13,7 @@ import theodolite.util.DeploymentFailedException
 class ResourceSets: KubernetesResource {
     @JsonProperty("configMap")
     @JsonInclude(JsonInclude.Include.NON_NULL)
-    var  configMap: ConfigMapResourceSet? = null
+    var configMap: ConfigMapResourceSet? = null
 
     @JsonProperty("fileSystem")
     @JsonInclude(JsonInclude.Include.NON_NULL)
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt
index 0187735b8fd273419874942cb7ed68797732c84c..fccbd2c41a646a2ef85ef77c65763e7f793d1e91 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt
@@ -1,7 +1,6 @@
 package theodolite.benchmark
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.annotation.JsonSerialize
 import io.quarkus.runtime.annotations.RegisterForReflection
 
 @JsonDeserialize
@@ -9,5 +8,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection
 class Resources {
 
     lateinit var resources: List<ResourceSets>
+    lateinit var beforeActions: List<Action>
+    lateinit var afterActions: List<Action>
 
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
index 281c68e318784ee8206473cd014f814b3f5152a9..be3e48be406b631e03ca2fd32909a442b592f259 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
@@ -1,6 +1,5 @@
 package theodolite.evaluation
 
-import mu.KotlinLogging
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.util.EvaluationFailedException
 import theodolite.util.IOHandler
@@ -12,8 +11,6 @@ import java.time.Instant
 import java.util.*
 import java.util.regex.Pattern
 
-private val logger = KotlinLogging.logger {}
-
 /**
  * Contains the analysis. Fetches a metric from Prometheus, documents it, and evaluates it.
  * @param slo Slo that is used for the analysis.
@@ -37,7 +34,6 @@ class AnalysisExecutor(
      *  @return true if the experiment succeeded.
      */
     fun analyze(load: LoadDimension, res: Resource, executionIntervals: List<Pair<Instant, Instant>>): Boolean {
-        var result: Boolean
         var repetitionCounter = 1
 
         try {
@@ -50,7 +46,7 @@ class AnalysisExecutor(
                     fetcher.fetchMetric(
                         start = interval.first,
                         end = interval.second,
-                        query = SloConfigHandler.getQueryString(sloType = slo.sloType)
+                        query = SloConfigHandler.getQueryString(slo = slo)
                     )
                 }
 
@@ -68,12 +64,11 @@ class AnalysisExecutor(
                 load = load
             )
 
-            result = sloChecker.evaluate(prometheusData)
+            return sloChecker.evaluate(prometheusData)
 
         } catch (e: Exception) {
-            throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()} ", e)
+            throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()}", e)
         }
-        return result
     }
 
     private val NONLATIN: Pattern = Pattern.compile("[^\\w-]")
@@ -83,6 +78,6 @@ class AnalysisExecutor(
         val noWhitespace: String = WHITESPACE.matcher(this).replaceAll("-")
         val normalized: String = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD)
         val slug: String = NONLATIN.matcher(normalized).replaceAll("")
-        return slug.toLowerCase(Locale.ENGLISH)
+        return slug.lowercase(Locale.ENGLISH)
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
index d646286b70bc5880df1f603afdc2bda22bcc3259..7fb5417e200f64b0db74a8bebe69a751c5d484b8 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
@@ -1,6 +1,5 @@
 package theodolite.evaluation
 
-import com.google.gson.Gson
 import khttp.post
 import mu.KotlinLogging
 import theodolite.util.PrometheusResponse
@@ -9,13 +8,11 @@ import java.net.ConnectException
 /**
  * [SloChecker] that uses an external source for the concrete evaluation.
  * @param externalSlopeURL The url under which the external evaluation can be reached.
- * @param threshold threshold that should not be exceeded to evaluate to true.
- * @param warmup time that is not taken into consideration for the evaluation.
+ * @param metadata metadata passed to the external SLO checker.
  */
 class ExternalSloChecker(
     private val externalSlopeURL: String,
-    private val threshold: Int,
-    private val warmup: Int
+    private val metadata: Map<String, Any>
 ) : SloChecker {
 
     private val RETRIES = 2
@@ -28,29 +25,25 @@ class ExternalSloChecker(
      * Will try to reach the external service until success or [RETRIES] times.
      * Each request will timeout after [TIMEOUT].
      *
-     * @param start point of the experiment.
-     * @param end point of the experiment.
      * @param fetchedData that should be evaluated
-     * @return true if the experiment was successful(the threshold was not exceeded.
+     * @return true if the experiment was successful (the threshold was not exceeded).
      * @throws ConnectException if the external service could not be reached.
      */
     override fun evaluate(fetchedData: List<PrometheusResponse>): Boolean {
         var counter = 0
-        val data = SloJson.Builder()
-            .results(fetchedData.map { it.data?.result })
-            .addMetadata("threshold", threshold)
-            .addMetadata( "warmup", warmup)
-            .build()
-            .toJson()
+        val data = SloJson(
+            results = fetchedData.map { it.data?.result ?: listOf() },
+            metadata = metadata
+        ).toJson()
 
         while (counter < RETRIES) {
             val result = post(externalSlopeURL, data = data, timeout = TIMEOUT)
             if (result.statusCode != 200) {
                 counter++
-                logger.error { "Could not reach external SLO checker" }
+                logger.error { "Could not reach external SLO checker." }
             } else {
                 val booleanResult = result.text.toBoolean()
-                logger.info { "SLO checker result is: $booleanResult" }
+                logger.info { "SLO checker result is: $booleanResult." }
                 return booleanResult
             }
         }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
index af70fa5dca3f0556d38791ed96c2af30b9a44a68..82f903f5be868731d58ebefd6279d5d438bd5eab 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
@@ -11,7 +11,7 @@ interface SloChecker {
      * Evaluates [fetchedData] and returns if the experiments were successful.
      *
      * @param fetchedData from Prometheus that will be evaluated.
-     * @return true if experiments were successful. Otherwise false.
+     * @return true if experiments were successful. Otherwise, false.
      */
     fun evaluate(fetchedData: List<PrometheusResponse>): Boolean
 }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
index 64f9110cd931feef41dc65f88d6623e82f4e03a2..f57cebfcb13d0e86919ec15a0a479d1258e318a6 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
@@ -43,15 +43,32 @@ class SloCheckerFactory {
         properties: MutableMap<String, String>,
         load: LoadDimension
     ): SloChecker {
-        return when (sloType.toLowerCase()) {
-            SloTypes.LAG_TREND.value, SloTypes.DROPPED_RECORDS.value -> ExternalSloChecker(
+        return when (SloTypes.from(sloType)) {
+            SloTypes.GENERIC -> ExternalSloChecker(
                 externalSlopeURL = properties["externalSloUrl"]
                     ?: throw IllegalArgumentException("externalSloUrl expected"),
-                threshold = properties["threshold"]?.toInt() ?: throw IllegalArgumentException("threshold expected"),
-                warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")
+                // TODO validate property contents
+                metadata = mapOf(
+                    "warmup" to (properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")),
+                    "queryAggregation" to (properties["queryAggregation"]
+                        ?: throw IllegalArgumentException("queryAggregation expected")),
+                    "repetitionAggregation" to (properties["repetitionAggregation"]
+                        ?: throw IllegalArgumentException("repetitionAggregation expected")),
+                    "operator" to (properties["operator"] ?: throw IllegalArgumentException("operator expected")),
+                    "threshold" to (properties["threshold"]?.toInt()
+                        ?: throw IllegalArgumentException("threshold expected"))
+                )
             )
-
-                SloTypes.LAG_TREND_RATIO.value, SloTypes.DROPPED_RECORDS_RATIO.value -> {
+            SloTypes.LAG_TREND, SloTypes.DROPPED_RECORDS -> ExternalSloChecker(
+                externalSlopeURL = properties["externalSloUrl"]
+                    ?: throw IllegalArgumentException("externalSloUrl expected"),
+                metadata = mapOf(
+                    "warmup" to (properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")),
+                    "threshold" to (properties["threshold"]?.toInt()
+                        ?: throw IllegalArgumentException("threshold expected"))
+                )
+            )
+            SloTypes.LAG_TREND_RATIO, SloTypes.DROPPED_RECORDS_RATIO -> {
                 val thresholdRatio =
                     properties["ratio"]?.toDouble()
                         ?: throw IllegalArgumentException("ratio for threshold expected")
@@ -64,11 +81,13 @@ class SloCheckerFactory {
                 ExternalSloChecker(
                     externalSlopeURL = properties["externalSloUrl"]
                         ?: throw IllegalArgumentException("externalSloUrl expected"),
-                    threshold = threshold,
-                    warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")
+                    metadata = mapOf(
+                        "warmup" to (properties["warmup"]?.toInt()
+                            ?: throw IllegalArgumentException("warmup expected")),
+                        "threshold" to threshold
+                    )
                 )
             }
-            else -> throw IllegalArgumentException("Slotype $sloType not found.")
         }
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
index 93929218c822030ff065dafb19cce1fbaa69a179..425a4f3b0634d53f8b1d5c4b8abdba9ca81c3f2b 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
@@ -1,5 +1,6 @@
 package theodolite.evaluation
 
+import theodolite.benchmark.BenchmarkExecution
 import theodolite.util.InvalidPatcherConfigurationException
 import javax.enterprise.context.ApplicationScoped
 
@@ -7,13 +8,14 @@ private const val CONSUMER_LAG_QUERY = "sum by(group)(kafka_consumergroup_group_
 private const val DROPPED_RECORDS_QUERY = "sum by(job) (kafka_streams_stream_task_metrics_dropped_records_total>=0)"
 
 @ApplicationScoped
-class SloConfigHandler() {
+class SloConfigHandler {
     companion object {
-        fun getQueryString(sloType: String): String {
-            return when (sloType.toLowerCase()) {
+        fun getQueryString(slo: BenchmarkExecution.Slo): String {
+            return when (slo.sloType.toLowerCase()) {
+                SloTypes.GENERIC.value -> slo.properties["promQLQuery"] ?: throw IllegalArgumentException("promQLQuery expected")
                 SloTypes.LAG_TREND.value, SloTypes.LAG_TREND_RATIO.value -> CONSUMER_LAG_QUERY
                 SloTypes.DROPPED_RECORDS.value, SloTypes.DROPPED_RECORDS_RATIO.value -> DROPPED_RECORDS_QUERY
-                else -> throw  InvalidPatcherConfigurationException("Could not find Prometheus query string for slo type $sloType")
+                else -> throw  InvalidPatcherConfigurationException("Could not find Prometheus query string for slo type $slo.sloType")
             }
         }
     }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
index fc9fe17b255dbb5ae68881538d8d2a50a191edb1..205389276f2c1adef6cba6c745baf99744c8d2dd 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
@@ -3,61 +3,17 @@ package theodolite.evaluation
 import com.google.gson.Gson
 import theodolite.util.PromResult
 
-class SloJson private constructor(
-    val results: List<List<PromResult>?>? = null,
-    var metadata: MutableMap<String, Any>? = null
+class SloJson constructor(
+    val results: List<List<PromResult>>,
+    var metadata: Map<String, Any>
 ) {
 
-    data class Builder(
-        var results:List<List<PromResult>?>? = null,
-        var metadata: MutableMap<String, Any>? = null
-    ) {
-
-        /**
-         *  Set the results
-         *
-         * @param results list of prometheus results
-         */
-        fun results(results: List<List<PromResult>?>) = apply { this.results = results }
-
-        /**
-         * Add metadata as key value pairs
-         *
-         * @param key key of the metadata to be added
-         * @param value value of the metadata to be added
-         */
-        fun addMetadata(key: String, value: String) = apply {
-            if (this.metadata.isNullOrEmpty()) {
-                this.metadata = mutableMapOf(key to value)
-            } else {
-                this.metadata!![key] = value
-            }
-        }
-
-        /**
-         * Add metadata as key value pairs
-         *
-         * @param key key of the metadata to be added
-         * @param value value of the metadata to be added
-         */
-        fun addMetadata(key: String, value: Int) = apply {
-            if (this.metadata.isNullOrEmpty()) {
-                this.metadata = mutableMapOf(key to value)
-            } else {
-                this.metadata!![key] = value
-            }
-        }
-
-        fun build() = SloJson(
-            results = results,
-            metadata = metadata
+    fun toJson(): String {
+        return Gson().toJson(
+            mapOf(
+                "results" to this.results,
+                "metadata" to this.metadata
+            )
         )
     }
-
-   fun  toJson(): String {
-       return Gson().toJson(mapOf(
-           "results" to this.results,
-           "metadata" to this.metadata
-       ))
-    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
index ac9de35861b0bd9c012bfb0b8cfcb2e1aa5aed68..812b50de779d2f3abfd5788b8aee145edc959e6c 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
@@ -1,10 +1,14 @@
 package theodolite.evaluation
 
 enum class SloTypes(val value: String) {
+    GENERIC("generic"),
     LAG_TREND("lag trend"),
     LAG_TREND_RATIO("lag trend ratio"),
     DROPPED_RECORDS("dropped records"),
-    DROPPED_RECORDS_RATIO("dropped records ratio")
-
+    DROPPED_RECORDS_RATIO("dropped records ratio");
 
+    companion object {
+        fun from(type: String): SloTypes =
+            values().find { it.value == type } ?: throw IllegalArgumentException("Requested SLO does not exist")
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
index bf947be01b534fd000d3967f0b72ef25978d4110..370b87e062d942a512e059ee4041dca776376ddf 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
@@ -2,6 +2,5 @@ package theodolite.execution
 
 enum class ExecutionModes(val value: String) {
     OPERATOR("operator"),
-    YAML_EXECUTOR("yaml-executor"),
     STANDALONE("standalone")
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/Main.kt b/theodolite/src/main/kotlin/theodolite/execution/Main.kt
index 11f696ddd739e987e92ecec724390948714d898b..17b3d4e7b86f9e430abfb6093e79aa7865cd5923 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/Main.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/Main.kt
@@ -17,8 +17,8 @@ object Main {
         val mode = Configuration.EXECUTION_MODE
         logger.info { "Start Theodolite with mode $mode" }
 
-        when (mode.toLowerCase()) {
-            ExecutionModes.STANDALONE.value, ExecutionModes.YAML_EXECUTOR.value -> TheodoliteStandalone().start()  // TODO remove standalone (#209)
+        when (mode.lowercase()) {
+            ExecutionModes.STANDALONE.value -> TheodoliteStandalone().start()
             ExecutionModes.OPERATOR.value -> TheodoliteOperator().start()
             else -> {
                 logger.error { "MODE $mode not found" }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt
index 6dedc94af864269d7d15929c69ec54aa384fc8e3..29ac39c122f68636e08c6c5ecd5a6c01751edafb 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt
@@ -14,14 +14,13 @@ private val logger = KotlinLogging.logger {}
  * @property benchmarkExecution
  * @property benchmark
  */
-class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) :
-    Thread() {
+class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) {
 
     /**
      * Run
      * Delete all Kubernetes resources which are related to the execution and the benchmark.
      */
-    override fun run() {
+    fun run() {
         // Build Configuration to teardown
         try {
             logger.info { "Received shutdown signal -> Shutting down" }
@@ -34,9 +33,7 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b
                     afterTeardownDelay = 5L
                 )
             deployment.teardown()
-            logger.info {
-                "Finished teardown of all benchmark resources."
-            }
+            logger.info { "Finished teardown of all benchmark resources." }
         } catch (e: Exception) {
             logger.warn {
                 "Could not delete all specified resources from Kubernetes. " +
diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
index 315d1cf1afe7fd2ffbfc1c437d725d4dff29f637..8596576e0a7984c32b6dabf90c6bbf06961d2bb1 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
@@ -137,6 +137,12 @@ class TheodoliteExecutor(
                 config.compositeStrategy.benchmarkExecutor.results,
                 "${resultsFolder}exp${this.config.executionId}-result"
             )
+            // Create expXYZ_demand.csv file
+            ioHandler.writeToCSVFile(
+                "${resultsFolder}exp${this.config.executionId}_demand",
+                calculateDemandMetric(config.loads, config.compositeStrategy.benchmarkExecutor.results),
+                listOf("load","resources")
+            )
         }
         kubernetesBenchmark.teardownInfrastructure()
     }
@@ -151,4 +157,8 @@ class TheodoliteExecutor(
         return executionID
     }
 
+    private fun calculateDemandMetric(loadDimensions: List<LoadDimension>, results: Results): List<List<String>> {
+        return loadDimensions.map { listOf(it.get().toString(), results.getMinRequiredInstances(it).get().toString()) }
+    }
+
 }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
index 0b5d6040bdea1316f8fb55bcc3f204c5443f6eee..93536282e2eefe6e476c3fde3fd86860fa24dcc3 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
@@ -2,8 +2,6 @@ package theodolite.execution.operator
 
 import io.fabric8.kubernetes.api.model.HasMetadata
 import io.fabric8.kubernetes.api.model.KubernetesResourceList
-import io.fabric8.kubernetes.api.model.Namespaced
-import io.fabric8.kubernetes.client.CustomResource
 import io.fabric8.kubernetes.client.KubernetesClientException
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.MixedOperation
@@ -12,30 +10,30 @@ import mu.KotlinLogging
 import java.lang.Thread.sleep
 private val logger = KotlinLogging.logger {}
 
-abstract class AbstractStateHandler<T, L, D>(
+private const val MAX_RETRIES: Int = 5
+
+abstract class AbstractStateHandler<S : HasMetadata>(
     private val client: NamespacedKubernetesClient,
-    private val crd: Class<T>,
-    private val crdList: Class<L>
-) : StateHandler<T> where T : CustomResource<*, *>?, T : HasMetadata, T : Namespaced, L : KubernetesResourceList<T> {
+    private val crd: Class<S>
+) {
 
-    private val crdClient: MixedOperation<T, L, Resource<T>> =
-        this.client.customResources(this.crd, this.crdList)
+    private val crdClient: MixedOperation<S, KubernetesResourceList<S>, Resource<S>> = this.client.resources(this.crd)
 
     @Synchronized
-    override fun setState(resourceName: String, f: (T) -> T?) {
+    fun setState(resourceName: String, setter: (S) -> S?) {
         try {
-            this.crdClient
-                .list().items
-                .filter { it.metadata.name == resourceName }
-                .map { customResource -> f(customResource) }
-                .forEach { this.crdClient.updateStatus(it) }
+            val resource = this.crdClient.withName(resourceName).get()
+            if (resource != null) {
+                val resourcePatched = setter(resource)
+                this.crdClient.patchStatus(resourcePatched)
+            }
         } catch (e: KubernetesClientException) {
-            logger.warn { "Status cannot be set for resource $resourceName" }
+            logger.warn(e) { "Status cannot be set for resource $resourceName." }
         }
     }
 
     @Synchronized
-    override fun getState(resourceName: String, f: (T) -> String?): String? {
+    fun getState(resourceName: String, f: (S) -> String?): String? {
         return this.crdClient
             .list().items
             .filter { it.metadata.name == resourceName }
@@ -44,13 +42,13 @@ abstract class AbstractStateHandler<T, L, D>(
     }
 
     @Synchronized
-    override fun blockUntilStateIsSet(
+    fun blockUntilStateIsSet(
         resourceName: String,
         desiredStatusString: String,
-        f: (T) -> String?,
-        maxTries: Int
+        f: (S) -> String?,
+        maxRetries: Int = MAX_RETRIES
     ): Boolean {
-        for (i in 0.rangeTo(maxTries)) {
+        for (i in 0.rangeTo(maxRetries)) {
             val currentStatus = getState(resourceName, f)
             if (currentStatus == desiredStatusString) {
                 return true
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6dcfb582655ff9295aedd63d8c30cbac7daae2b3
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt
@@ -0,0 +1,201 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.api.model.apps.Deployment
+import io.fabric8.kubernetes.api.model.apps.StatefulSet
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient
+import io.fabric8.kubernetes.client.dsl.MixedOperation
+import io.fabric8.kubernetes.client.dsl.Resource
+import theodolite.benchmark.Action
+import theodolite.benchmark.ActionSelector
+import theodolite.benchmark.KubernetesBenchmark
+import theodolite.benchmark.ResourceSets
+import theodolite.model.crd.BenchmarkCRD
+import theodolite.model.crd.BenchmarkState
+import theodolite.model.crd.KubernetesBenchmarkList
+
+class BenchmarkStateChecker(
+    private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
+    private val benchmarkStateHandler: BenchmarkStateHandler,
+    private val client: NamespacedKubernetesClient
+
+) {
+
+    fun start(running: Boolean) {
+        Thread {
+            while (running) {
+                updateBenchmarkStatus()
+                Thread.sleep(1000)
+            }
+        }.start()
+    }
+
+    /**
+     * Checks and updates the states off all deployed benchmarks.
+     *
+     */
+    fun updateBenchmarkStatus() {
+        this.benchmarkCRDClient
+            .list()
+            .items
+            .map { it.spec.name = it.metadata.name; it }
+            .map { Pair(it, checkState(it.spec)) }
+            .forEach { setState(it.first, it.second) }
+    }
+
+    private fun setState(resource: BenchmarkCRD, state: BenchmarkState) {
+        benchmarkStateHandler.setResourceSetState(resource.spec.name, state)
+    }
+
+    /**
+     * Checks the state of the benchmark.
+     *
+     * @param benchmark The benchmark to check
+     * @return [BenchmarkStates.READY] iff all resource could be loaded and all actions could be executed, [BenchmarkStates.PENDING] else
+     */
+    private fun checkState(benchmark: KubernetesBenchmark): BenchmarkState {
+        return if (checkActionCommands(benchmark) == BenchmarkState.READY
+            && checkResources(benchmark) == BenchmarkState.READY
+        ) {
+            BenchmarkState.READY
+        } else {
+            BenchmarkState.PENDING
+        }
+    }
+
+    /**
+     * Checks if all specified actions of the given benchmark could be executed or not
+     *
+     * @param benchmark The benchmark to check
+     * @return The state of this benchmark. [BenchmarkStates.READY] if all actions could be executed, else [BenchmarkStates.PENDING]
+     */
+    private fun checkActionCommands(benchmark: KubernetesBenchmark): BenchmarkState {
+        return if (checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.beforeActions)
+            && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.afterActions)
+            && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions)
+            && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions)
+        ) {
+            BenchmarkState.READY
+        } else {
+            BenchmarkState.PENDING
+        }
+    }
+
+    /**
+     * Action commands are called on a pod. To verify that an action command can be executed,
+     * it checks that the specified pods are either currently running in the cluster or
+     * have been specified as infrastructure in the benchmark.
+     *
+     * @param benchmark the benchmark to check
+     * @param actions the actions
+     * @return true if all actions could be executed, else false
+     */
+    private fun checkIfActionPossible(resourcesSets: List<ResourceSets>, actions: List<Action>): Boolean {
+        return !actions.map {
+            checkIfResourceIsDeployed(it.selector) || checkIfResourceIsInfrastructure(resourcesSets, it.selector)
+        }.contains(false)
+    }
+
+    /**
+     * Checks for the given actionSelector whether the required resources are already deployed in the cluster or not
+     *
+     * @param selector the actionSelector to check
+     * @return true if the required resources are found, else false
+     */
+    fun checkIfResourceIsDeployed(selector: ActionSelector): Boolean {
+        val pods = this.client
+            .pods()
+            .withLabels(selector.pod.matchLabels)
+            .list()
+            .items
+
+        return if (pods.isNotEmpty() && selector.container.isNotEmpty()) {
+            pods.map { pod ->
+                pod
+                    .spec
+                    .containers
+                    .map { it.name }
+                    .contains(selector.container)
+            }.contains(true)
+        } else {
+            pods.isNotEmpty()
+        }
+    }
+
+    /**
+     * Checks for the given actionSelector whether the required resources are specified as infrastructure or not
+     *
+     * @param benchmark the benchmark to check
+     * @param selector the actionSelector to check
+     * @return true if the required resources are found, else false
+     */
+    fun checkIfResourceIsInfrastructure(resourcesSets: List<ResourceSets>, selector: ActionSelector): Boolean {
+        val resources = resourcesSets.flatMap { it.loadResourceSet(this.client) }
+        if (resources.isEmpty()) {
+            return false
+        }
+
+        var podExist = resources.map { it.second }
+            .filterIsInstance<Deployment>()
+            .filter { it.metadata.labels.containsMatchLabels(selector.pod.matchLabels) }
+            .any {
+                if (selector.container.isNotEmpty()) {
+                    it.spec.template.spec.containers.map { it.name }.contains(selector.container)
+                } else {
+                    true
+                }
+            }
+
+        if (podExist) {
+            return true
+        }
+
+        podExist = resources.map { it.second }
+            .filterIsInstance<StatefulSet>()
+            .filter { it.metadata.labels.containsMatchLabels(selector.pod.matchLabels) }
+            .any {
+                if (selector.container.isNotEmpty()) {
+                    it.spec.template.spec.containers.map { it.name }.contains(selector.container)
+                } else {
+                    true
+                }
+            }
+
+        if (podExist) {
+            return true
+        }
+
+        return false
+    }
+
+    /**
+     * Checks if it is possible to load all specified Kubernetes manifests.
+     *
+     * @param benchmark The benchmark to check
+     * @return The state of this benchmark. [BenchmarkState.READY] if all resources could be loaded, else [BenchmarkState.PENDING]
+     */
+    fun checkResources(benchmark: KubernetesBenchmark): BenchmarkState {
+        return try {
+            val appResources =
+                benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources)
+            val loadGenResources =
+                benchmark.loadKubernetesResources(resourceSet = benchmark.loadGenerator.resources)
+            if (appResources.isNotEmpty() && loadGenResources.isNotEmpty()) {
+                BenchmarkState.READY
+            } else {
+                BenchmarkState.PENDING
+            }
+        } catch (e: Exception) {
+            BenchmarkState.PENDING
+        }
+    }
+}
+
+private fun <K, V> MutableMap<K, V>.containsMatchLabels(matchLabels: MutableMap<V, V>): Boolean {
+    for (kv in matchLabels) {
+        if (kv.value != this[kv.key as K]) {
+            return false
+        }
+    }
+    return true
+}
+
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
index adca2a8b7fdb9b3e610f15e57c011679869df14c..3b46859737d86a34b58a5514c0ae31ae215b9c7d 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
@@ -4,25 +4,24 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import theodolite.model.crd.*
 
 class BenchmarkStateHandler(val client: NamespacedKubernetesClient) :
-    AbstractStateHandler<BenchmarkCRD, KubernetesBenchmarkList, ExecutionStatus>(
+    AbstractStateHandler<BenchmarkCRD>(
         client = client,
-        crd = BenchmarkCRD::class.java,
-        crdList = KubernetesBenchmarkList::class.java
+        crd = BenchmarkCRD::class.java
     ) {
 
-    private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState }
+    private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState.value }
 
-    fun setResourceSetState(resourceName: String, status: BenchmarkStates): Boolean {
-        setState(resourceName) { cr -> cr.status.resourceSetsState = status.value; cr }
+    fun setResourceSetState(resourceName: String, status: BenchmarkState): Boolean {
+        setState(resourceName) { cr -> cr.status.resourceSetsState = status; cr }
         return blockUntilStateIsSet(resourceName, status.value, getBenchmarkResourceState())
     }
 
-    fun getResourceSetState(resourceName: String): ExecutionStates {
+    fun getResourceSetState(resourceName: String): ExecutionState {
         val status = this.getState(resourceName, getBenchmarkResourceState())
         return if (status.isNullOrBlank()) {
-            ExecutionStates.NO_STATE
+            ExecutionState.NO_STATE
         } else {
-            ExecutionStates.values().first { it.value == status }
+            ExecutionState.values().first { it.value == status }
         }
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
index efca98f8bf72024daa0367c6c57574f0644872e4..885315df6eda0d91a27567720056738b997a8ec1 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
@@ -3,14 +3,11 @@ package theodolite.execution.operator
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.MixedOperation
 import io.fabric8.kubernetes.client.dsl.Resource
-import mu.KotlinLogging
 import theodolite.execution.Shutdown
 import theodolite.k8s.K8sContextFactory
 import theodolite.k8s.ResourceByLabelHandler
 import theodolite.model.crd.*
 
-private val logger = KotlinLogging.logger {}
-
 class ClusterSetup(
     private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
     private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
@@ -41,7 +38,7 @@ class ClusterSetup(
             .list()
             .items
             .asSequence()
-            .filter { it.status.executionState == ExecutionStates.RUNNING.value }
+            .filter { it.status.executionState == ExecutionState.RUNNING }
             .forEach { execution ->
                 val benchmark = benchmarkCRDClient
                     .inNamespace(client.namespace)
@@ -52,9 +49,9 @@ class ClusterSetup(
                 if (benchmark != null) {
                     execution.spec.name = execution.metadata.name
                     benchmark.spec.name = benchmark.metadata.name
-                    Shutdown(execution.spec, benchmark.spec).start()
+                    Shutdown(execution.spec, benchmark.spec).run()
                 } else {
-                    throw IllegalStateException("Execution with state ${ExecutionStates.RUNNING.value} was found, but no corresponding benchmark. " +
+                    throw IllegalStateException("Execution with state ${ExecutionState.RUNNING.value} was found, but no corresponding benchmark. " +
                             "Could not initialize cluster.")
                 }
             }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
index 16c4ea98ba614bb3dcdd7d9f486f4e65ae70d380..25c627a350e3939530c4b453ec6db846b546cc08 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
@@ -17,25 +17,26 @@ private val logger = KotlinLogging.logger {}
  * @see TheodoliteController
  * @see BenchmarkExecution
  */
-class ExecutionHandler(
+class ExecutionEventHandler(
     private val controller: TheodoliteController,
     private val stateHandler: ExecutionStateHandler
 ) : ResourceEventHandler<ExecutionCRD> {
+
     private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create()
 
     /**
-     * Add an execution to the end of the queue of the TheodoliteController.
+     * Adds an execution to the end of the queue of the TheodoliteController.
      *
-     * @param ExecutionCRD the execution to add
+     * @param execution the execution to add
      */
     @Synchronized
     override fun onAdd(execution: ExecutionCRD) {
-        logger.info { "Add execution ${execution.metadata.name}" }
+        logger.info { "Add execution ${execution.metadata.name}." }
         execution.spec.name = execution.metadata.name
         when (this.stateHandler.getExecutionState(execution.metadata.name)) {
-            ExecutionStates.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.PENDING)
-            ExecutionStates.RUNNING -> {
-                this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.RESTART)
+            ExecutionState.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.PENDING)
+            ExecutionState.RUNNING -> {
+                this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.RESTART)
                 if (this.controller.isExecutionRunning(execution.spec.name)) {
                     this.controller.stop(restart = true)
                 }
@@ -44,29 +45,29 @@ class ExecutionHandler(
     }
 
     /**
-     * Updates an execution. If this execution is running at the time this function is called, it is stopped and
+     * To be called on update of an execution. If this execution is running at the time this function is called, it is stopped and
      * added to the beginning of the queue of the TheodoliteController.
      * Otherwise, it is just added to the beginning of the queue.
      *
-     * @param oldExecutionCRD the old execution
-     * @param newExecutionCRD the new execution
+     * @param oldExecution the old execution
+     * @param newExecution the new execution
      */
     @Synchronized
     override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) {
         newExecution.spec.name = newExecution.metadata.name
         oldExecution.spec.name = oldExecution.metadata.name
         if (gson.toJson(oldExecution.spec) != gson.toJson(newExecution.spec)) {
-            logger.info { "Receive update event for execution ${oldExecution.metadata.name}" }
+            logger.info { "Receive update event for execution ${oldExecution.metadata.name}." }
             when (this.stateHandler.getExecutionState(newExecution.metadata.name)) {
-                ExecutionStates.RUNNING -> {
-                    this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.RESTART)
+                ExecutionState.RUNNING -> {
+                    this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.RESTART)
                     if (this.controller.isExecutionRunning(newExecution.spec.name)) {
                         this.controller.stop(restart = true)
                     }
                 }
-                ExecutionStates.RESTART -> {
+                ExecutionState.RESTART -> {
                 } // should this set to pending?
-                else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.PENDING)
+                else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.PENDING)
             }
         }
     }
@@ -74,12 +75,12 @@ class ExecutionHandler(
     /**
      * Delete an execution from the queue of the TheodoliteController.
      *
-     * @param ExecutionCRD the execution to delete
+     * @param execution the execution to delete
      */
     @Synchronized
-    override fun onDelete(execution: ExecutionCRD, b: Boolean) {
-        logger.info { "Delete execution ${execution.metadata.name}" }
-        if (execution.status.executionState == ExecutionStates.RUNNING.value
+    override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) {
+        logger.info { "Delete execution ${execution.metadata.name}." }
+        if (execution.status.executionState == ExecutionState.RUNNING
             && this.controller.isExecutionRunning(execution.metadata.name)
         ) {
             this.controller.stop()
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt
index 9f49cf3ee4f9f62e7006dbf6697340e1af152f27..340044e5be954d4d7673120e5bf2cba5aed02d92 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt
@@ -1,80 +1,54 @@
 package theodolite.execution.operator
 
+import io.fabric8.kubernetes.api.model.MicroTime
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
-import theodolite.model.crd.BenchmarkExecutionList
 import theodolite.model.crd.ExecutionCRD
-import theodolite.model.crd.ExecutionStatus
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 import java.lang.Thread.sleep
-import java.time.Duration
 import java.time.Instant
 import java.util.concurrent.atomic.AtomicBoolean
 
 class ExecutionStateHandler(val client: NamespacedKubernetesClient) :
-    AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus>(
+    AbstractStateHandler<ExecutionCRD>(
         client = client,
-        crd = ExecutionCRD::class.java,
-        crdList = BenchmarkExecutionList::class.java
+        crd = ExecutionCRD::class.java
     ) {
 
     private var runExecutionDurationTimer: AtomicBoolean = AtomicBoolean(false)
 
-    private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState }
+    private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState.value }
 
-    private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration }
-
-    fun setExecutionState(resourceName: String, status: ExecutionStates): Boolean {
-        setState(resourceName) { cr -> cr.status.executionState = status.value; cr }
+    fun setExecutionState(resourceName: String, status: ExecutionState): Boolean {
+        super.setState(resourceName) { cr -> cr.status.executionState = status; cr }
         return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda())
     }
 
-    fun getExecutionState(resourceName: String): ExecutionStates {
-        val status = this.getState(resourceName, getExecutionLambda())
-        return if (status.isNullOrBlank()) {
-            ExecutionStates.NO_STATE
-        } else {
-            ExecutionStates.values().first { it.value == status }
-        }
-    }
-
-    fun setDurationState(resourceName: String, duration: Duration): Boolean {
-        setState(resourceName) { cr -> cr.status.executionDuration = durationToK8sString(duration); cr }
-        return blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda())
+    fun getExecutionState(resourceName: String): ExecutionState {
+        val statusString = this.getState(resourceName, getExecutionLambda())
+        return ExecutionState.values().first { it.value == statusString }
     }
 
-    fun getDurationState(resourceName: String): String {
-        val status = getState(resourceName, getDurationLambda())
-        return if (status.isNullOrBlank()) {
-            "-"
-        } else {
-            status
-        }
-    }
-
-    private fun durationToK8sString(duration: Duration): String {
-        val sec = duration.seconds
-        return when {
-            sec <= 120 -> "${sec}s" // max 120s
-            sec < 60 * 99 -> "${duration.toMinutes()}m" // max 99m
-            sec < 60 * 60 * 99 -> "${duration.toHours()}h"   // max 99h
-            else -> "${duration.toDays()}d + ${duration.minusDays(duration.toDays()).toHours()}h"
-        }
+    private fun updateDurationState(resourceName: String) {
+        super.setState(resourceName) { cr -> cr }
     }
 
     fun startDurationStateTimer(resourceName: String) {
         this.runExecutionDurationTimer.set(true)
-        val startTime = Instant.now().toEpochMilli()
+
+        super.setState(resourceName) { cr -> cr.status.completionTime = null; cr }
+        super.setState(resourceName) { cr -> cr.status.startTime = MicroTime(Instant.now().toString()); cr }
+
         Thread {
             while (this.runExecutionDurationTimer.get()) {
-                val duration = Duration.ofMillis(Instant.now().minusMillis(startTime).toEpochMilli())
-                setDurationState(resourceName, duration)
+                updateDurationState(resourceName)
                 sleep(100 * 1)
             }
         }.start()
     }
 
     @Synchronized
-    fun stopDurationStateTimer() {
+    fun stopDurationStateTimer(resourceName: String) {
+        super.setState(resourceName) { cr -> cr.status.completionTime = MicroTime(Instant.now().toString()); cr }
         this.runExecutionDurationTimer.set(false)
         sleep(100 * 2)
     }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
index e2cfaa354443cdc940abf92ef2c7474d028daecf..28563ac5a640d0226224b812a8e0691cde83942a 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
@@ -1,15 +1,19 @@
 package theodolite.execution.operator
 
-private const val MAX_TRIES: Int = 5
+private const val MAX_RETRIES: Int = 5
 
+@Deprecated("should not be needed")
 interface StateHandler<T> {
+
     fun setState(resourceName: String, f: (T) -> T?)
+
     fun getState(resourceName: String, f: (T) -> String?): String?
+
     fun blockUntilStateIsSet(
         resourceName: String,
         desiredStatusString: String,
         f: (T) -> String?,
-        maxTries: Int = MAX_TRIES
+        maxRetries: Int = MAX_RETRIES
     ): Boolean
 
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
index 70e30cf84ef40796eb085a0d68eb2e323232fde9..5f4180b0b4b58fa94b979c71998314baae63a91b 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
@@ -22,26 +22,24 @@ const val CREATED_BY_LABEL_VALUE = "theodolite"
  *
  * @see BenchmarkExecution
  * @see KubernetesBenchmark
- * @see ConcurrentLinkedDeque
  */
 
 class TheodoliteController(
     private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
     private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
     private val executionStateHandler: ExecutionStateHandler,
-    private val benchmarkStateHandler: BenchmarkStateHandler
+    private val benchmarkStateChecker: BenchmarkStateChecker
 ) {
     lateinit var executor: TheodoliteExecutor
 
     /**
-     *
      * Runs the TheodoliteController forever.
      */
     fun run() {
         sleep(5000) // wait until all states are correctly set
+        benchmarkStateChecker.start(true)
         while (true) {
             reconcile()
-            updateBenchmarkStatus()
             sleep(2000)
         }
     }
@@ -49,7 +47,6 @@ class TheodoliteController(
     private fun reconcile() {
         do {
             val execution = getNextExecution()
-            updateBenchmarkStatus()
             if (execution != null) {
                 val benchmark = getBenchmarks()
                     .map { it.spec }
@@ -88,40 +85,39 @@ class TheodoliteController(
             labelName = CREATED_BY_LABEL_NAME
         )
 
-        executionStateHandler.setExecutionState(execution.name, ExecutionStates.RUNNING)
+        executionStateHandler.setExecutionState(execution.name, ExecutionState.RUNNING)
         executionStateHandler.startDurationStateTimer(execution.name)
 
             executor = TheodoliteExecutor(execution, benchmark)
             executor.run()
             when (executionStateHandler.getExecutionState(execution.name)) {
-                ExecutionStates.RESTART -> runExecution(execution, benchmark)
-                ExecutionStates.RUNNING -> {
-                    executionStateHandler.setExecutionState(execution.name, ExecutionStates.FINISHED)
+                ExecutionState.RESTART -> runExecution(execution, benchmark)
+                ExecutionState.RUNNING -> {
+                    executionStateHandler.setExecutionState(execution.name, ExecutionState.FINISHED)
                     logger.info { "Execution of ${execution.name} is finally stopped." }
                     }
                 else -> {
-                    executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE)
-                    logger.warn { "Unexpected execution state, set state to ${ExecutionStates.FAILURE.value}" }
+                    executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE)
+                    logger.warn { "Unexpected execution state, set state to ${ExecutionState.FAILURE.value}." }
                 }
             }
         } catch (e: Exception) {
-                EventCreator().createEvent(
+            EventCreator().createEvent(
                 executionName = execution.name,
                 type = "WARNING",
                 reason = "Execution failed",
                 message = "An error occurs while executing:  ${e.message}")
-            logger.error { "Failure while executing execution ${execution.name} with benchmark ${benchmark.name}." }
-            logger.error { "Problem is: $e" }
-            executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE)
+            logger.error(e) { "Failure while executing execution ${execution.name} with benchmark ${benchmark.name}." }
+            executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE)
         }
-        executionStateHandler.stopDurationStateTimer()
+        executionStateHandler.stopDurationStateTimer(execution.name)
     }
 
     @Synchronized
     fun stop(restart: Boolean = false) {
         if (!::executor.isInitialized) return
         if (restart) {
-            executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionStates.RESTART)
+            executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionState.RESTART)
         }
         this.executor.executor.run.set(false)
     }
@@ -139,22 +135,21 @@ class TheodoliteController(
             }
     }
 
-
     /**
      * Get the [BenchmarkExecution] for the next run. Which [BenchmarkExecution]
      * is selected for the next execution depends on three points:
      *
      * 1. Only executions are considered for which a matching benchmark is available on the cluster
-     * 2. The Status of the execution must be [ExecutionStates.PENDING] or [ExecutionStates.RESTART]
-     * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionStates.RESTART] are preferred,
+     * 2. The Status of the execution must be [ExecutionState.PENDING] or [ExecutionState.RESTART]
+     * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionState.RESTART] are preferred,
      * then, if there is more than one, the oldest execution is chosen.
      *
      * @return the next execution or null
      */
     private fun getNextExecution(): BenchmarkExecution? {
-        val comparator = ExecutionStateComparator(ExecutionStates.RESTART)
+        val comparator = ExecutionStateComparator(ExecutionState.RESTART)
         val availableBenchmarkNames = getBenchmarks()
-            .filter { it.status.resourceSetsState == BenchmarkStates.READY.value }
+            .filter { it.status.resourceSetsState == BenchmarkState.READY }
             .map { it.spec }
             .map { it.name }
 
@@ -164,8 +159,7 @@ class TheodoliteController(
             .asSequence()
             .map { it.spec.name = it.metadata.name; it }
             .filter {
-                it.status.executionState == ExecutionStates.PENDING.value ||
-                        it.status.executionState == ExecutionStates.RESTART.value
+                it.status.executionState == ExecutionState.PENDING || it.status.executionState == ExecutionState.RESTART
             }
             .filter { availableBenchmarkNames.contains(it.spec.benchmark) }
             .sortedWith(comparator.thenBy { it.metadata.creationTimestamp })
@@ -173,35 +167,6 @@ class TheodoliteController(
             .firstOrNull()
     }
 
-    private fun updateBenchmarkStatus() {
-        this.benchmarkCRDClient
-            .list()
-            .items
-            .map { it.spec.name = it.metadata.name; it }
-            .map { Pair(it, checkResource(it.spec)) }
-            .forEach { setState(it.first, it.second ) }
-    }
-
-    private fun setState(resource: BenchmarkCRD, state: BenchmarkStates) {
-        benchmarkStateHandler.setResourceSetState(resource.spec.name, state)
-    }
-
-    private fun checkResource(benchmark: KubernetesBenchmark): BenchmarkStates {
-        return try {
-            val appResources =
-                benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources)
-            val loadGenResources =
-                benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources)
-            if(appResources.isNotEmpty() && loadGenResources.isNotEmpty()) {
-                BenchmarkStates.READY
-            } else {
-                BenchmarkStates.PENDING
-            }
-        } catch (e: Exception) {
-            BenchmarkStates.PENDING
-        }
-    }
-
     fun isExecutionRunning(executionName: String): Boolean {
         if (!::executor.isInitialized) return false
         return this.executor.getExecution().name == executionName
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
index 4850a44fdddba117178e29d3170f44a95df646e7..071bd06071345499d01595df72e5de4c8535b3fc 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
@@ -34,6 +34,7 @@ class TheodoliteOperator {
     private lateinit var controller: TheodoliteController
     private lateinit var executionStateHandler: ExecutionStateHandler
     private lateinit var benchmarkStateHandler: BenchmarkStateHandler
+    private lateinit var benchmarkStateChecker: BenchmarkStateChecker
 
 
     fun start() {
@@ -71,7 +72,7 @@ class TheodoliteOperator {
             controller = getController(
                 client = client,
                 executionStateHandler = getExecutionStateHandler(client = client),
-                benchmarkStateHandler = getBenchmarkStateHandler(client = client)
+                benchmarkStateChecker = getBenchmarkStateChecker(client = client)
 
             )
             getExecutionEventHandler(controller, client).startAllRegisteredInformers()
@@ -90,7 +91,7 @@ class TheodoliteOperator {
             ExecutionCRD::class.java,
             RESYNC_PERIOD
         ).addEventHandler(
-            ExecutionHandler(
+            ExecutionEventHandler(
                 controller = controller,
                 stateHandler = ExecutionStateHandler(client)
             )
@@ -112,17 +113,28 @@ class TheodoliteOperator {
         return benchmarkStateHandler
     }
 
+    fun getBenchmarkStateChecker(client: NamespacedKubernetesClient) : BenchmarkStateChecker {
+        if (!::benchmarkStateChecker.isInitialized) {
+            this.benchmarkStateChecker = BenchmarkStateChecker(
+                client = client,
+                benchmarkStateHandler = getBenchmarkStateHandler(client = client),
+                benchmarkCRDClient = getBenchmarkClient(client = client))
+        }
+        return benchmarkStateChecker
+    }
+
+
     fun getController(
         client: NamespacedKubernetesClient,
         executionStateHandler: ExecutionStateHandler,
-        benchmarkStateHandler: BenchmarkStateHandler
+        benchmarkStateChecker: BenchmarkStateChecker
     ): TheodoliteController {
         if (!::controller.isInitialized) {
             this.controller = TheodoliteController(
                 benchmarkCRDClient = getBenchmarkClient(client),
                 executionCRDClient = getExecutionClient(client),
                 executionStateHandler = executionStateHandler,
-                benchmarkStateHandler = benchmarkStateHandler
+                benchmarkStateChecker = benchmarkStateChecker
             )
         }
         return this.controller
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
index 28a72c8947bffe7b57203cacf2460d7080fa7b51..518b8eae211dd064e3c12b0713382bf3b12bb1ba 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
@@ -96,10 +96,9 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) {
     /**
      * Block until all pods with are deleted
      *
-     * @param [labelName] the label name
-     * @param [labelValue] the value of this label
+     * @param matchLabels Map of label keys to label values to be deleted
      * */
-    fun blockUntilPodsDeleted(matchLabels: MutableMap<String, String>) {
+    fun blockUntilPodsDeleted(matchLabels: Map<String, String>) {
         while (
             !this.client
                 .pods()
@@ -108,7 +107,7 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) {
                 .items
                 .isNullOrEmpty()
         ) {
-            logger.info { "Wait for pods with label ${matchLabels.toString()} to be deleted." }
+            logger.info { "Wait for pods with label $matchLabels to be deleted." }
             Thread.sleep(1000)
         }
     }
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt
index f2afd71f6e4b4cf8e7106a8fc8a9bd113d9f36e6..ed1e06571d20c53fc82439833c8a31800a48b602 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt
@@ -99,7 +99,7 @@ class TopicManager(private val kafkaConfig: Map<String, Any>) {
 
             val toDelete = topics.filter { kafkaAdmin.listTopics().names().get().contains(it) }
 
-            if (toDelete.isNullOrEmpty()) {
+            if (toDelete.isEmpty()) {
                 deleted = true
             } else {
                 logger.info { "Deletion of Kafka topics failed, will retry in ${RETRY_TIME / 1000} seconds." }
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
index 862de14e2a7a4721e15215b0a1389e14f943fe24..871b8cf43907fcb8b0b5ea501c6b47f82e56ff69 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
@@ -9,7 +9,7 @@ private val logger = KotlinLogging.logger {}
 abstract class AbstractK8sLoader: K8sResourceLoader {
 
     fun loadK8sResource(kind: String, resourceString: String): KubernetesResource {
-        return when (kind.replaceFirst(kind[0],kind[0].toUpperCase())) {
+        return when (kind.replaceFirst(kind[0],kind[0].uppercaseChar())) {
             "Deployment" -> loadDeployment(resourceString)
             "Service" -> loadService(resourceString)
             "ServiceMonitor" -> loadServiceMonitor(resourceString)
@@ -18,13 +18,13 @@ abstract class AbstractK8sLoader: K8sResourceLoader {
             "Execution" -> loadExecution(resourceString)
             "Benchmark" -> loadBenchmark(resourceString)
             else -> {
-                logger.error { "Error during loading of unspecified resource Kind $kind" }
-                throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind")
+                logger.error { "Error during loading of unspecified resource Kind '$kind'." }
+                throw IllegalArgumentException("error while loading resource with kind: $kind")
             }
         }
     }
 
-    fun <T> loadGenericResource(resourceString: String, f: (String) -> T): T {
+    fun <T : KubernetesResource> loadGenericResource(resourceString: String, f: (String) -> T): T {
         var resource: T? = null
 
         try {
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
index e9611aaa82870dfb676820029cf42c5aab63d672..639e4c4584d47968cd718d601f1cd7064d85eda2 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
@@ -2,42 +2,37 @@ package theodolite.k8s.resourceLoader
 
 import io.fabric8.kubernetes.api.model.ConfigMap
 import io.fabric8.kubernetes.api.model.KubernetesResource
+import io.fabric8.kubernetes.api.model.Service
 import io.fabric8.kubernetes.api.model.apps.Deployment
+import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.util.YamlParserFromString
 import java.io.ByteArrayInputStream
+import java.io.InputStream
 
 class K8sResourceLoaderFromString(private val client: NamespacedKubernetesClient): AbstractK8sLoader(),
     K8sResourceLoader {
 
-    @OptIn(ExperimentalStdlibApi::class)
-    override fun loadService(resource: String): KubernetesResource {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.services().load(stream).get() }
+    override fun loadService(resource: String): Service {
+        return loadAnyResource(resource) { stream -> client.services().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun loadDeployment(resource: String): Deployment {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.apps().deployments().load(stream).get() }
+        return loadAnyResource(resource) { stream -> client.apps().deployments().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun loadConfigmap(resource: String): ConfigMap {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.configMaps().load(stream).get() }
+        return loadAnyResource(resource) { stream -> client.configMaps().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
-    override fun loadStatefulSet(resource: String): KubernetesResource {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.apps().statefulSets().load(stream).get() }
+    override fun loadStatefulSet(resource: String): StatefulSet {
+        return loadAnyResource(resource) { stream -> client.apps().statefulSets().load(stream).get() }
+    }
+
+    private fun <T : KubernetesResource> loadAnyResource(resource: String, f: (InputStream) -> T): T {
+        return loadGenericResource(resource) { f(ByteArrayInputStream(it.encodeToByteArray())) }
     }
 
     /**
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
index b6468fff523e57b124e144d5b9fef6477973655a..0ec6decbdea5e192721a4f9b6d0d85ea65665a5a 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
@@ -1,7 +1,6 @@
 package theodolite.model.crd
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import io.fabric8.kubernetes.api.model.HasMetadata
 import io.fabric8.kubernetes.api.model.Namespaced
 import io.fabric8.kubernetes.client.CustomResource
 import io.fabric8.kubernetes.model.annotation.Group
@@ -13,7 +12,14 @@ import theodolite.benchmark.KubernetesBenchmark
 @Version("v1")
 @Group("theodolite.com")
 @Kind("benchmark")
-class BenchmarkCRD(
-    var spec: KubernetesBenchmark = KubernetesBenchmark(),
-    var status: BenchmarkStatus = BenchmarkStatus()
-) : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced, HasMetadata
\ No newline at end of file
+class BenchmarkCRD : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced {
+
+    override fun initSpec(): KubernetesBenchmark {
+        return KubernetesBenchmark()
+    }
+
+    override fun initStatus(): BenchmarkStatus {
+        return BenchmarkStatus()
+    }
+
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dc2c6f9ba971367c0bb142a54745629eb29c07d5
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt
@@ -0,0 +1,8 @@
+package theodolite.model.crd
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum class BenchmarkState(@JsonValue val value: String) {
+    PENDING("Pending"),
+    READY("Ready")
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt
deleted file mode 100644
index f52f2c168765ebb8bcc4f390795aa470b968021b..0000000000000000000000000000000000000000
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package theodolite.model.crd
-
-enum class BenchmarkStates(val value: String) {
-    PENDING("Pending"),
-    READY("Ready")
-}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
index f51cb7a76d015d6ecd900279e68d41baa26e876a..d4a17dbefb6cf3a53d545c32cb18e1d9acd7067f 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
@@ -6,6 +6,6 @@ import io.fabric8.kubernetes.api.model.Namespaced
 
 @JsonDeserialize
 class BenchmarkStatus: KubernetesResource, Namespaced {
-    var resourceSetsState = "-"
+    var resourceSetsState: BenchmarkState = BenchmarkState.PENDING
 
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
index 659621e8c3b1d5308a10d81240575dd3d432b53f..3be0aaf2a30cd4ef279edd34854eb936cc6e7e7c 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
@@ -12,7 +12,14 @@ import theodolite.benchmark.BenchmarkExecution
 @Version("v1")
 @Group("theodolite.com")
 @Kind("execution")
-class ExecutionCRD(
-    var spec: BenchmarkExecution = BenchmarkExecution(),
-    var status: ExecutionStatus = ExecutionStatus()
-) : CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced
+class ExecutionCRD: CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced {
+
+    override fun initSpec(): BenchmarkExecution {
+        return BenchmarkExecution()
+    }
+
+    override fun initStatus(): ExecutionStatus {
+         return ExecutionStatus()
+    }
+
+}
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
similarity index 65%
rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt
rename to theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
index ad68bf380b18af1a654c201817bb7fc982804c8b..9ce38d9f56a968ccc408966e56609ee4f70570a4 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
@@ -1,7 +1,8 @@
 package theodolite.model.crd
 
-enum class ExecutionStates(val value: String) {
-    // Execution states
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum class ExecutionState(@JsonValue val value: String) {
     RUNNING("Running"),
     PENDING("Pending"),
     FAILURE("Failure"),
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
index 252738959762aa5d0732babc5589c698d7bd4e9f..1f843ccf9152676e778bc4ed359776e37205e998 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
@@ -1,11 +1,58 @@
 package theodolite.model.crd
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.databind.JsonSerializer
+import com.fasterxml.jackson.databind.SerializerProvider
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import io.fabric8.kubernetes.api.model.Duration
 import io.fabric8.kubernetes.api.model.KubernetesResource
+import io.fabric8.kubernetes.api.model.MicroTime
 import io.fabric8.kubernetes.api.model.Namespaced
+import java.time.Clock
+import java.time.Instant
+import java.time.Duration as JavaDuration
+
 
 @JsonDeserialize
-class ExecutionStatus : KubernetesResource, Namespaced {
-    var executionState: String = ""
-    var executionDuration: String = "-"
+@JsonIgnoreProperties(ignoreUnknown = true)
+class ExecutionStatus(
+    private val clock: Clock = Clock.systemUTC()
+) : KubernetesResource, Namespaced {
+
+    var executionState: ExecutionState = ExecutionState.NO_STATE
+
+    var startTime: MicroTime? = null
+
+    var completionTime: MicroTime? = null
+
+    @get:JsonSerialize(using = DurationSerializer::class)
+    val executionDuration: Duration?
+        get() {
+            val startTime = this.startTime?.toInstant()
+            val completionTime = this.completionTime?.toInstant() ?: clock.instant()!!
+            return startTime?.let {Duration(JavaDuration.between(it, completionTime)) }
+        }
+
+    private fun MicroTime.toInstant(): Instant {
+        return Instant.parse(this.time)
+    }
+
+    class DurationSerializer : JsonSerializer<Duration?>() {
+
+        override fun serialize(duration: Duration?, generator: JsonGenerator, serProvider: SerializerProvider) {
+            generator.writeObject(duration?.duration?.toK8sString())
+        }
+
+        private fun JavaDuration.toK8sString(): String {
+            return when {
+                this <= JavaDuration.ofSeconds(2)  -> "${this.toSeconds()}s"
+                this < JavaDuration.ofMinutes(99) -> "${this.toMinutes()}m"
+                this < JavaDuration.ofHours(99) -> "${this.toHours()}h"
+                else -> "${this.toDays()}d + ${this.minusDays(this.toDays()).toHours()}h"
+            }
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
index ebad5de74a6b819dbf7887dfad91faac37ed5074..88b3e19e999a889cdcb8345ca7c90c37a6e6d275 100644
--- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
+++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
@@ -1,7 +1,6 @@
 package theodolite.patcher
 
 import io.fabric8.kubernetes.api.model.KubernetesResource
-import theodolite.util.DeploymentFailedException
 import theodolite.util.InvalidPatcherConfigurationException
 import theodolite.util.PatcherDefinition
 
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
index 41cc5c325163ade54469398e815fdb8d95c6e6cd..d6ace6f564239e73a0d59f8eb7900f50018482c5 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
@@ -23,7 +23,7 @@ class CompositeStrategy(
     override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
         var restrictedResources = resources.toList()
         for (strategy in this.restrictionStrategies) {
-            restrictedResources = restrictedResources.intersect(strategy.apply(load, resources)).toList()
+            restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList()
         }
         return this.searchStrategy.findSuitableResource(load, restrictedResources)
     }
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
index cb0dd2d8ab528e42e8290f59f26c8b9b32f384c7..83c4abbdf44f1a1c2f3a27714d796580feedee49 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
@@ -22,7 +22,7 @@ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmar
         for (res in resources) {
             logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
             val result = this.benchmarkExecutor.runExperiment(load, res)
-            if (result && minimalSuitableResources != null) {
+            if (result && minimalSuitableResources == null) {
                 minimalSuitableResources = res
             }
         }
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt
new file mode 100644
index 0000000000000000000000000000000000000000..786a3baf159e94841c1f76c696f030718e8f768f
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt
@@ -0,0 +1,22 @@
+package theodolite.strategies.searchstrategy
+
+import io.quarkus.runtime.annotations.RegisterForReflection
+import theodolite.util.Resource
+
+/**
+ * Base class for the implementation of Guess strategies. Guess strategies are strategies to determine the resource
+ * demand we start with in our initial guess search strategy.
+ */
+
+@RegisterForReflection
+abstract class GuessStrategy {
+    /**
+     * Computing the resource demand for the initial guess search strategy to start with.
+     *
+     * @param resources List of all possible [Resource]s.
+     * @param lastLowestResource Previous resource demand needed for the given load.
+     *
+     * @return Returns the resource demand to start the initial guess search strategy with or null
+     */
+    abstract fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource?
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d97fb62cc9d37dd50122199e5d089c491784e511
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt
@@ -0,0 +1,93 @@
+package theodolite.strategies.searchstrategy
+
+import mu.KotlinLogging
+import theodolite.execution.BenchmarkExecutor
+import theodolite.util.LoadDimension
+import theodolite.util.Resource
+import theodolite.util.Results
+
+private val logger = KotlinLogging.logger {}
+
+/**
+ *  Search strategy implementation for determining the smallest suitable resource demand.
+ *  Starting with a resource amount provided by a guess strategy.
+ *
+ * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks.
+ * @param guessStrategy Strategy that provides us with a guess for the first resource amount.
+ * @param results current results of all previously performed benchmarks.
+ */
+class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStrategy: GuessStrategy, results: Results) :
+        SearchStrategy(benchmarkExecutor, guessStrategy, results) {
+
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
+
+        if(resources.isEmpty()) {
+            logger.info { "You need to specify resources to be checked for the InitialGuessSearchStrategy to work." }
+            return null
+        }
+
+        if(guessStrategy == null){
+            logger.info { "Your InitialGuessSearchStrategy doesn't have a GuessStrategy. This is not supported." }
+            return null
+        }
+
+        if(results == null){
+            logger.info { "The results need to be initialized." }
+            return null
+        }
+
+
+        var lastLowestResource : Resource? = null
+
+        // Getting the lastLowestResource from results and calling firstGuess() with it
+        if (!results.isEmpty()) {
+            val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load)
+            lastLowestResource = this.results.getMinRequiredInstances(maxLoad)
+            if (lastLowestResource.get() == Int.MAX_VALUE) lastLowestResource = null
+        }
+        lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource)
+
+        if (lastLowestResource != null) {
+            val resourcesToCheck: List<Resource>
+            val startIndex: Int = resources.indexOf(lastLowestResource)
+
+            logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" }
+
+            // If the first experiment passes, starting downward linear search
+            // otherwise starting upward linear search
+            if (this.benchmarkExecutor.runExperiment(load, lastLowestResource)) {
+
+                resourcesToCheck = resources.subList(0, startIndex).reversed()
+                if (resourcesToCheck.isEmpty()) return lastLowestResource
+
+                var currentMin: Resource = lastLowestResource
+                for (res in resourcesToCheck) {
+
+                    logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
+                    if (this.benchmarkExecutor.runExperiment(load, res)) {
+                        currentMin = res
+                    }
+                }
+                return currentMin
+            }
+            else {
+                if (resources.size <= startIndex + 1) {
+                    logger.info{ "No more resources left to check." }
+                    return null
+                }
+                resourcesToCheck = resources.subList(startIndex + 1, resources.size)
+
+                for (res in resourcesToCheck) {
+
+                    logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
+                    if (this.benchmarkExecutor.runExperiment(load, res)) return res
+                }
+            }
+        }
+        else {
+            logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " +
+                    "starting point!" }
+        }
+        return null
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt
new file mode 100644
index 0000000000000000000000000000000000000000..413eecea27279cd79bad155fbb7d5d18b674a12e
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt
@@ -0,0 +1,24 @@
+package theodolite.strategies.searchstrategy
+
+import theodolite.util.Resource
+
+/**
+ * This Guess strategy takes the minimal resource demand of the previous load, which is given as an argument for the
+ * firstGuess function.
+ */
+
+class PrevResourceMinGuess() : GuessStrategy(){
+
+    /**
+     * @param resources List of all possible [Resource]s.
+     * @param lastLowestResource Previous resource demand needed for the given load.
+     *
+     * @return the value of lastLowestResource if given otherwise the first element of the resource list or null
+     */
+    override fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? {
+
+        if (lastLowestResource != null) return lastLowestResource
+        else if(resources.isNotEmpty()) return resources[0]
+        else return null
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
index 4e304b010d4d56f6b5fe734a6b977361f93e57a1..97c723f2cfe459081cbb327f6860e48319c8f4f1 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
@@ -4,14 +4,18 @@ import io.quarkus.runtime.annotations.RegisterForReflection
 import theodolite.execution.BenchmarkExecutor
 import theodolite.util.LoadDimension
 import theodolite.util.Resource
+import theodolite.util.Results
 
 /**
  *  Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number of instances.
  *
  * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks.
+ * @param guessStrategy Guess strategy for the initial resource amount in case the InitialGuessStrategy is selected.
+ * @param results the [Results] object.
  */
 @RegisterForReflection
-abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) {
+abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor, val guessStrategy: GuessStrategy? = null,
+                              val results: Results? = null) {
     /**
      * Find smallest suitable resource from the specified resource list for the given load.
      *
diff --git a/theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c1a8fc401961370d2f07bfffe43f0ae4dc441d25
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt
@@ -0,0 +1,4 @@
+package theodolite.util
+
+class ActionCommandFailedException(message: String, e: Exception? = null) : DeploymentFailedException(message,e) {
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
index dac3b943e69bd7e208d318f2a788275f19db11e4..0a63cfa84de9e60fba04707372ef884d77a1543b 100644
--- a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
@@ -7,12 +7,16 @@ private const val DEFAULT_NAMESPACE = "default"
 private const val DEFAULT_COMPONENT_NAME = "theodolite-operator"
 
 
-class Configuration(
-) {
+class Configuration {
     companion object {
         val NAMESPACE = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
         val COMPONENT_NAME = System.getenv("COMPONENT_NAME") ?: DEFAULT_COMPONENT_NAME
         val EXECUTION_MODE = System.getenv("MODE") ?: ExecutionModes.STANDALONE.value
+
+        /**
+         * Specifies how long Theodolite should wait (in sec) before aborting the execution of an action command.
+         */
+        const val TIMEOUT_SECONDS: Long = 30L
     }
 
 }
diff --git a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
index c67ed7ffd79afc733a97dae05c3203f8e78722ea..ebdf5a37b4e82c8d4b1870d065f5e77133154735 100644
--- a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
@@ -1,4 +1,3 @@
 package theodolite.util
 
-class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e) {
-}
+class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e)
diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
index 6566a451a3e273214f59962531b6bd17b33a850d..2e181dad35786d386226f8a57dfffbc2c3966754 100644
--- a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
@@ -1,4 +1,3 @@
 package theodolite.util
 
-open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) {
-}
\ No newline at end of file
+open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e)
diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
index 8a6b0e9a49362afa401cf3c1279e7f7f6cddf85d..81bf350b58901bc10535f143d5ccdb295b5fe85f 100644
--- a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
@@ -1,18 +1,17 @@
 package theodolite.util
 
 import theodolite.model.crd.ExecutionCRD
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
-class ExecutionStateComparator(private val preferredState: ExecutionStates): Comparator<ExecutionCRD> {
+class ExecutionStateComparator(private val preferredState: ExecutionState): Comparator<ExecutionCRD> {
 
     /**
-     * Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with
-     * status [ExecutionStates.RESTART] are before all other executions.
+     * Simple comparator which can be used to order a list of [ExecutionCRD]s such that executions with
+     * status [ExecutionState.RESTART] are before all other executions.
      */
     override fun compare(p0: ExecutionCRD, p1: ExecutionCRD): Int {
        return when {
-            (p0 == null && p1 == null) -> 0
-            (p0.status.executionState == preferredState.value) -> -1
+            (p0.status.executionState == preferredState) -> -1
             else -> 1
         }
     }
diff --git a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
index 57032189412d0937e4d77ddbf4354c78ffcc71a3..8b580c733ab7ae527d99c676223f4b09b392c6fd 100644
--- a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
@@ -85,7 +85,7 @@ class IOHandler {
      * @param data the data to write in the file as String
      */
     fun writeStringToTextFile(fileURL: String, data: String) {
-        val outputFile = File("$fileURL")
+        val outputFile = File(fileURL)
         outputFile.printWriter().use {
             it.println(data)
         }
diff --git a/theodolite/src/main/kotlin/theodolite/util/Results.kt b/theodolite/src/main/kotlin/theodolite/util/Results.kt
index 60641ea0248435de53aaaaf362da7be995b391c5..2221c2e64f6dbc1776122f20793aa8d04d621d9d 100644
--- a/theodolite/src/main/kotlin/theodolite/util/Results.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/Results.kt
@@ -3,7 +3,7 @@ package theodolite.util
 import io.quarkus.runtime.annotations.RegisterForReflection
 
 /**
- * Central class that saves the state of a execution of Theodolite. For an execution, it is used to save the result of
+ * Central class that saves the state of an execution of Theodolite. For an execution, it is used to save the result of
  * individual experiments. Further, it is used by the RestrictionStrategy to
  * perform the [theodolite.strategies.restriction.RestrictionStrategy].
  */
@@ -44,16 +44,16 @@ class Results {
      * If no experiments have been marked as either successful or unsuccessful
      * yet, a Resource with the constant value Int.MIN_VALUE is returned.
      */
-    fun getMinRequiredInstances(load: LoadDimension?): Resource? {
+    fun getMinRequiredInstances(load: LoadDimension?): Resource {
         if (this.results.isEmpty()) {
             return Resource(Int.MIN_VALUE, emptyList())
         }
 
-        var minRequiredInstances: Resource? = Resource(Int.MAX_VALUE, emptyList())
+        var minRequiredInstances = Resource(Int.MAX_VALUE, emptyList())
         for (experiment in results) {
             // Get all successful experiments for requested load
             if (experiment.key.first == load && experiment.value) {
-                if (minRequiredInstances == null || experiment.key.second.get() < minRequiredInstances.get()) {
+                if (experiment.key.second.get() < minRequiredInstances.get()) {
                     // Found new smallest resources
                     minRequiredInstances = experiment.key.second
                 }
@@ -83,4 +83,13 @@ class Results {
         }
         return maxBenchmarkedLoad
     }
+
+    /**
+     * Checks whether the results are empty.
+     *
+     * @return true if [results] is empty.
+     */
+    fun isEmpty(): Boolean{
+        return results.isEmpty()
+    }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
index 61db189ee99fa5fe36113b0fdecf589ad1114852..0e197908a501c0f6b89761a61989580b18e21f64 100644
--- a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
@@ -2,9 +2,6 @@ package theodolite.util
 
 import org.yaml.snakeyaml.Yaml
 import org.yaml.snakeyaml.constructor.Constructor
-import java.io.File
-import java.io.FileInputStream
-import java.io.InputStream
 
 /**
  * The YamlParser parses a YAML string
diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1af6f548b219697009c688ace712a9f7f5620bd0
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt
@@ -0,0 +1,133 @@
+package theodolite
+
+import io.quarkus.test.junit.QuarkusTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import theodolite.benchmark.BenchmarkExecution
+import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy
+import theodolite.util.LoadDimension
+import theodolite.util.Resource
+import theodolite.util.Results
+import mu.KotlinLogging
+import theodolite.strategies.searchstrategy.PrevResourceMinGuess
+
+private val logger = KotlinLogging.logger {}
+
+@QuarkusTest
+class InitialGuessSearchStrategyTest {
+
+    @Test
+    fun testInitialGuessSearch() {
+        val mockResults = arrayOf(
+            arrayOf(true, true, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, false, true, true, true, true),
+            arrayOf(false, false, false, false, true, true, true),
+            arrayOf(false, false, false, false, false, false, true),
+            arrayOf(false, false, false, false, false, false, false)
+        )
+        val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) }
+        val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) }
+        val results = Results()
+        val benchmark = TestBenchmark()
+        val guessStrategy = PrevResourceMinGuess()
+        val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo()
+        val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5)
+        val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results)
+
+        val actual: ArrayList<Resource?> = ArrayList()
+        val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) })
+        expected.add(null)
+
+        for (load in mockLoads) {
+            val returnVal : Resource? = strategy.findSuitableResource(load, mockResources)
+            if(returnVal != null) {
+                logger.info { "returnVal '${returnVal.get()}'" }
+            }
+            else {
+                logger.info { "returnVal is null." }
+            }
+            actual.add(returnVal)
+        }
+
+        assertEquals(actual, expected)
+    }
+
+    @Test
+    fun testInitialGuessSearchLowerResourceDemandHigherLoad() {
+        val mockResults = arrayOf(
+            arrayOf(true, true, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, true, true, true, true, true, true),
+            arrayOf(false, false, false, false, true, true, true),
+            arrayOf(false, false, false, false, false, false, true),
+            arrayOf(false, false, false, false, false, false, false)
+        )
+        val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) }
+        val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) }
+        val results = Results()
+        val benchmark = TestBenchmark()
+        val guessStrategy = PrevResourceMinGuess()
+        val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo()
+        val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5)
+        val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results)
+
+        val actual: ArrayList<Resource?> = ArrayList()
+        val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 1, 4, 6).map { x -> Resource(x, emptyList()) })
+        expected.add(null)
+
+        for (load in mockLoads) {
+            val returnVal : Resource? = strategy.findSuitableResource(load, mockResources)
+            if(returnVal != null) {
+                logger.info { "returnVal '${returnVal.get()}'" }
+            }
+            else {
+                logger.info { "returnVal is null." }
+            }
+            actual.add(returnVal)
+        }
+
+        assertEquals(actual, expected)
+    }
+
+    @Test
+    fun testInitialGuessSearchFirstNotDoable() {
+        val mockResults = arrayOf(
+                arrayOf(false, false, false, false, false, false, false),
+                arrayOf(false, false, true, true, true, true, true),
+                arrayOf(false, false, false, true, true, true, true),
+                arrayOf(true, true, true, true, true, true, true),
+                arrayOf(false, false, false, false, true, true, true),
+                arrayOf(false, false, false, false, false, false, true),
+                arrayOf(false, false, false, false, false, false, false)
+        )
+        val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) }
+        val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) }
+        val results = Results()
+        val benchmark = TestBenchmark()
+        val guessStrategy = PrevResourceMinGuess()
+        val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo()
+        val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5)
+        val strategy = InitialGuessSearchStrategy(benchmarkExecutor, guessStrategy, results)
+
+        val actual: ArrayList<Resource?> = ArrayList()
+        var expected: ArrayList<Resource?> = ArrayList(listOf(2, 3, 0, 4, 6).map { x -> Resource(x, emptyList()) })
+        expected.add(null)
+        expected = ArrayList(listOf(null) + expected)
+
+        for (load in mockLoads) {
+            val returnVal : Resource? = strategy.findSuitableResource(load, mockResources)
+            if(returnVal != null) {
+                logger.info { "returnVal '${returnVal.get()}'" }
+            }
+            else {
+                logger.info { "returnVal is null." }
+            }
+            actual.add(returnVal)
+        }
+
+        assertEquals(actual, expected)
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
index 46758583172c3fcd6417e17ff5bab85f8659734b..b7fc2d9f1b2d5110f974b3805584baa3903d5eb1 100644
--- a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
@@ -3,7 +3,7 @@ package theodolite
 import io.fabric8.kubernetes.api.model.apps.Deployment
 import io.fabric8.kubernetes.client.DefaultKubernetesClient
 import io.quarkus.test.junit.QuarkusTest
-import io.smallrye.common.constraint.Assert.assertTrue
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.patcher.PatcherFactory
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0e40fca5caf9fe721c547e09d2ba22c25860a1bf
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt
@@ -0,0 +1,128 @@
+package theodolite.benchmark
+
+import io.fabric8.kubernetes.api.model.Pod
+import io.fabric8.kubernetes.api.model.PodBuilder
+import io.fabric8.kubernetes.api.model.PodListBuilder
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.fabric8.kubernetes.client.utils.Utils
+import io.quarkus.test.junit.QuarkusTest
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import theodolite.execution.operator.TheodoliteController
+import theodolite.execution.operator.TheodoliteOperator
+import theodolite.util.ActionCommandFailedException
+
+@QuarkusTest
+class ActionCommandTest {
+    private val server = KubernetesServer(false, false)
+    lateinit var controller: TheodoliteController
+
+    @BeforeEach
+    fun setUp() {
+        server.before()
+        val operator = TheodoliteOperator()
+        this.controller = operator.getController(
+            client = server.client,
+            executionStateHandler = operator.getExecutionStateHandler(client = server.client),
+            benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client)
+        )
+
+        val pod: Pod = PodBuilder().withNewMetadata()
+            .withName("pod1")
+            .withResourceVersion("1")
+            .withLabels<String, String>(mapOf("app" to "pod"))
+            .withNamespace("test").and()
+            .build()
+
+        val ready: Pod = createReadyFrom(pod, "True")
+
+        val podList = PodListBuilder().build()
+        podList.items.add(0, ready)
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods?labelSelector=${Utils.toUrlEncoded("app=pod")}")
+            .andReturn(200, podList)
+            .always()
+
+        server
+            .expect()
+            .get()
+            .withPath("/api/v1/namespaces/test/pods/pod1")
+            .andReturn(200, ready)
+            .always()
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods/pod1/exec?command=ls&stdout=true&stderr=true")
+            .andUpgradeToWebSocket()
+            .open(ErrorChannelMessage("{\"metadata\":{},\"status\":\"Success\"}\n"))
+            .done()
+            .always()
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods/pod1/exec?command=error-command&stdout=true&stderr=true")
+            .andUpgradeToWebSocket()
+            .open(ErrorChannelMessage("{\"metadata\":{},\"status\":\"failed\", \"details\":{}}\n"))
+            .done()
+            .always()
+    }
+
+    /**
+     * Copied from fabric8 Kubernetes Client repository
+     *
+     * @param pod
+     * @param status
+     * @return
+     */
+    fun createReadyFrom(pod: Pod, status: String): Pod {
+        return PodBuilder(pod)
+            .withNewStatus()
+            .addNewCondition()
+            .withType("Ready")
+            .withStatus(status)
+            .endCondition()
+            .endStatus()
+            .build()
+    }
+
+    @AfterEach
+    fun tearDown() {
+        server.after()
+    }
+
+    @Test
+    fun testGetPodName() {
+        assertEquals("pod1", ActionCommand(client = server.client).getPodName(mutableMapOf("app" to "pod"), 1))
+    }
+
+    @Test
+    fun testActionSuccess() {
+        val action = Action()
+        action.selector = ActionSelector()
+        action.selector.pod = PodSelector()
+        action.selector.pod.matchLabels = mutableMapOf("app" to "pod")
+        action.exec = Command()
+        action.exec.command = arrayOf("ls")
+        action.exec.timeoutSeconds = 10L
+
+        action.exec(server.client)
+        assertEquals(
+            "/api/v1/namespaces/test/pods/pod1/exec?command=ls&stdout=true&stderr=true",
+            server.lastRequest.path)
+    }
+
+    @Test
+    fun testActionFailed() {
+        val action = Action()
+        action.selector = ActionSelector()
+        action.selector.pod = PodSelector()
+        action.selector.pod.matchLabels = mutableMapOf("app" to "pod")
+        action.exec = Command()
+        action.exec.command = arrayOf("error-command")
+        action.exec.timeoutSeconds = 10L
+
+        assertThrows<ActionCommandFailedException> { run { action.exec(server.client) } }
+    }
+}
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
index 2cc8f931418e28ae8841b592f93df8d88440cf3c..bc3263aa5fd06a8a19609d9f677db51f173cf54f 100644
--- a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
@@ -8,16 +8,17 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
-import io.smallrye.common.constraint.Assert.assertTrue
-import junit.framework.Assert.assertEquals
 import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.util.DeploymentFailedException
 
-private val testResourcePath = "./src/test/resources/k8s-resource-files/"
+private const val testResourcePath = "./src/test/resources/k8s-resource-files/"
 
 @QuarkusTest
 class ConfigMapResourceSetTest {
@@ -206,21 +207,17 @@ class ConfigMapResourceSetTest {
 
         val createdResourcesSet = resourceSet.getResourceSet(server.client)
 
-        assertEquals(1,createdResourcesSet.size )
+        assertEquals(1, createdResourcesSet.size )
         assert(createdResourcesSet.toMutableSet().first().second is Deployment)
     }
 
 
-    @Test()
+    @Test
     fun testConfigMapNotExist() {
         val resourceSet = ConfigMapResourceSet()
         resourceSet.name = "test-configmap1"
-        lateinit var ex: Exception
-        try {
+        assertThrows<DeploymentFailedException> {
             resourceSet.getResourceSet(server.client)
-        } catch (e: Exception) {
-            ex = e
         }
-        assertTrue(ex is DeploymentFailedException)
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt
new file mode 100644
index 0000000000000000000000000000000000000000..df57a2529653a39ccbde14b4a91d30352224457e
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt
@@ -0,0 +1,17 @@
+package theodolite.benchmark
+
+import io.fabric8.mockwebserver.internal.WebSocketMessage
+import java.nio.charset.StandardCharsets
+
+class ErrorChannelMessage(body: String) : WebSocketMessage(0L, getBodyBytes(OUT_STREAM_ID, body), true, true) {
+    companion object {
+        private const val OUT_STREAM_ID: Byte = 3
+        private fun getBodyBytes(prefix: Byte, body: String): ByteArray {
+            val original = body.toByteArray(StandardCharsets.UTF_8)
+            val prefixed = ByteArray(original.size + 1)
+            prefixed[0] = prefix
+            System.arraycopy(original, 0, prefixed, 1, original.size)
+            return prefixed
+        }
+    }
+}
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
index 59ad2be3248f67442ce352788f8b94b26f3b6b90..f15685c8e0ecd67b99caabb77f68cc35a78b47f2 100644
--- a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
@@ -5,16 +5,16 @@ import io.fabric8.kubernetes.api.model.Service
 import io.fabric8.kubernetes.api.model.apps.Deployment
 import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
-import io.smallrye.common.constraint.Assert.assertTrue
-import junit.framework.Assert.assertEquals
 import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.util.DeploymentFailedException
-import java.lang.IllegalStateException
 
-private val testResourcePath = "./src/test/resources/k8s-resource-files/"
+private const val testResourcePath = "./src/test/resources/k8s-resource-files/"
 
 class FileSystemResourceSetTest {
 
@@ -104,12 +104,8 @@ class FileSystemResourceSetTest {
     fun testWrongPath() {
         val resourceSet = FileSystemResourceSet()
         resourceSet.path = "/abc/not-exist"
-        lateinit var ex: Exception
-        try {
+        assertThrows<DeploymentFailedException> {
             resourceSet.getResourceSet(server.client)
-        } catch (e: Exception) {
-            println(e)
-            ex = e
         }
-        assertTrue(ex is DeploymentFailedException)    }
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
index e294ea539ea60104cc00e9f73de790302ad52670..cbddbfbfc5d6f838677c6d04b0a0c79f59d8bc66 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
@@ -8,7 +8,7 @@ import theodolite.util.KafkaConfig
 class BenchmarkCRDummy(name: String) {
 
     private val benchmark = KubernetesBenchmark()
-    private val benchmarkCR = BenchmarkCRD(benchmark)
+    private val benchmarkCR = BenchmarkCRD()
 
     fun getCR(): BenchmarkCRD {
         return benchmarkCR
@@ -34,6 +34,13 @@ class BenchmarkCRDummy(name: String) {
         benchmark.sut.resources = emptyList()
         benchmark.loadGenerator.resources = emptyList()
 
+        benchmark.infrastructure.beforeActions = emptyList()
+        benchmark.infrastructure.afterActions = emptyList()
+        benchmark.sut.beforeActions = emptyList()
+        benchmark.sut.afterActions = emptyList()
+        benchmark.loadGenerator.beforeActions = emptyList()
+        benchmark.loadGenerator.afterActions = emptyList()
+
         benchmark.resourceTypes = emptyList()
         benchmark.loadTypes = emptyList()
         benchmark.kafkaConfig = kafkaConfig
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..528cfac8066c28bf6382fb97cddf280b3c1de622
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt
@@ -0,0 +1,177 @@
+package theodolite.execution.operator
+
+import com.google.gson.Gson
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder
+import io.fabric8.kubernetes.api.model.Pod
+import io.fabric8.kubernetes.api.model.PodBuilder
+import io.fabric8.kubernetes.api.model.PodListBuilder
+import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.fabric8.kubernetes.client.server.mock.OutputStreamMessage
+import io.fabric8.kubernetes.client.utils.Utils
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.Assertions.*
+import theodolite.benchmark.*
+import theodolite.model.crd.BenchmarkState
+
+internal class BenchmarkStateCheckerTest {
+    private val server = KubernetesServer(false, false)
+    private val serverCrud = KubernetesServer(false, true)
+    private lateinit var checker: BenchmarkStateChecker
+    private lateinit var checkerCrud: BenchmarkStateChecker
+
+    @BeforeEach
+    fun setUp() {
+        server.before()
+        serverCrud.before()
+        val operator = TheodoliteOperator()
+        checker = BenchmarkStateChecker(
+            client = server.client,
+            benchmarkCRDClient = operator.getBenchmarkClient(server.client),
+            benchmarkStateHandler = operator.getBenchmarkStateHandler(server.client)
+        )
+
+        checkerCrud = BenchmarkStateChecker(
+            client = serverCrud.client,
+            benchmarkCRDClient = operator.getBenchmarkClient(serverCrud.client),
+            benchmarkStateHandler = operator.getBenchmarkStateHandler(serverCrud.client)
+        )
+
+        val pod: Pod = PodBuilder().withNewMetadata()
+            .withName("pod1")
+            .withResourceVersion("1")
+            .withLabels<String, String>(mapOf("app" to "pod"))
+            .withNamespace("test").and()
+            .build()
+
+        val ready: Pod = createReadyFrom(pod, "True")
+
+        val podList = PodListBuilder().build()
+        podList.items.add(0, ready)
+
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods?labelSelector=${Utils.toUrlEncoded("app=pod1")}")
+            .andReturn(200, podList)
+            .always()
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods?labelSelector=${Utils.toUrlEncoded("app=pod0")}")
+            .andReturn(200, emptyMap<String, String>())
+            .always()
+
+
+        server
+            .expect()
+            .get()
+            .withPath("/api/v1/namespaces/test/pods/pod1")
+            .andReturn(200, ready)
+            .always()
+
+        server
+            .expect()
+            .withPath("/api/v1/namespaces/test/pods/pod1/exec?command=ls&stdout=true&stderr=true")
+            .andUpgradeToWebSocket()
+            .open(OutputStreamMessage("Test-Output"))
+            .done()
+            .always()
+    }
+
+    @AfterEach
+    fun tearDown() {
+        server.after()
+        serverCrud.after()
+    }
+
+    /**
+     * Copied from fabric8 Kubernetes Client repository
+     *
+     * @param pod
+     * @param status
+     * @return
+     */
+    private fun createReadyFrom(pod: Pod, status: String): Pod {
+        return PodBuilder(pod)
+            .withNewStatus()
+            .addNewCondition()
+            .withType("Ready")
+            .withStatus(status)
+            .endCondition()
+            .endStatus()
+            .build()
+    }
+
+    private fun getActionSelector(label: Pair<String, String>): ActionSelector {
+        val podSelector = PodSelector()
+        val actionSelector = ActionSelector()
+        actionSelector.pod = podSelector
+
+        // pod with matching labels are deployed
+        podSelector.matchLabels = mutableMapOf(label)
+        return actionSelector
+    }
+
+    private fun createAndDeployConfigmapResourceSet(): ResourceSets {
+        // create test deployment
+        val resourceBuilder = DeploymentBuilder()
+        resourceBuilder.withNewSpec().endSpec()
+        resourceBuilder.withNewMetadata().endMetadata()
+        val resource = resourceBuilder.build()
+        resource.metadata.name = "test-deployment"
+        resource.metadata.labels = mutableMapOf("app" to "pod1")
+        val resourceString = Gson().toJson(resource)
+
+        // create and deploy configmap
+        val configMap1 = ConfigMapBuilder()
+            .withNewMetadata().withName("test-configmap").endMetadata()
+            .addToData("test-resource.yaml",resourceString)
+            .build()
+
+        serverCrud.client.configMaps().createOrReplace(configMap1)
+
+        // create configmap resource set
+        val resourceSet = ConfigMapResourceSet()
+        resourceSet.name = "test-configmap"
+
+        // create ResourceSetsList
+        val set = ResourceSets()
+        set.configMap = resourceSet
+        return set
+    }
+
+    @Test
+    fun checkIfResourceIsDeployed() {
+        // pod with matching labels are deployed
+        assertTrue(checker.checkIfResourceIsDeployed(getActionSelector("app" to "pod1")))
+
+        // no pod with matching labels are deployed
+        assertFalse(checker.checkIfResourceIsDeployed(getActionSelector("app" to "pod0")))
+    }
+
+    @Test
+    fun checkIfResourceIsInfrastructure() {
+        val resourceSets = listOf(createAndDeployConfigmapResourceSet())
+        assertTrue(checkerCrud.checkIfResourceIsInfrastructure(resourceSets, getActionSelector("app" to "pod1")))
+        assertFalse(checkerCrud.checkIfResourceIsInfrastructure(resourceSets, getActionSelector("app" to "pod0")))
+
+    }
+
+    @Test
+    fun checkResources() {
+        val benchmark = BenchmarkCRDummy(
+            name = "test-benchmark"
+        )
+        benchmark.getCR().spec.setClient(serverCrud.client)
+        val resourceSet = Resources()
+        resourceSet.resources = listOf(createAndDeployConfigmapResourceSet())
+        benchmark.getCR().spec.infrastructure = resourceSet
+        benchmark.getCR().spec.loadGenerator = resourceSet
+        benchmark.getCR().spec.sut = resourceSet
+
+        assertEquals(BenchmarkState.READY,checkerCrud.checkResources(benchmark.getCR().spec))
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
index 7e0532aff36cac2fb1a1c718415315b8f54052c2..7d40f7e45d6aa2c93206a1bad22754fe93b0c100 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.benchmark.KubernetesBenchmark
 import theodolite.model.crd.BenchmarkCRD
-import theodolite.model.crd.BenchmarkStates
+import theodolite.model.crd.BenchmarkState
 import theodolite.model.crd.ExecutionCRD
 
 @QuarkusTest
@@ -32,15 +32,16 @@ class ControllerTest {
     @BeforeEach
     fun setUp() {
         server.before()
-        this.controller = TheodoliteOperator().getController(
+        val operator = TheodoliteOperator()
+        this.controller = operator.getController(
             client = server.client,
-            executionStateHandler = ExecutionStateHandler(server.client),
-            benchmarkStateHandler =  BenchmarkStateHandler(server.client)
+            executionStateHandler = operator.getExecutionStateHandler(client = server.client),
+            benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client)
         )
 
         // benchmark
         val benchmark1 = BenchmarkCRDummy(name = "Test-Benchmark")
-        benchmark1.getCR().status.resourceSetsState = BenchmarkStates.READY.value
+        benchmark1.getCR().status.resourceSetsState = BenchmarkState.READY
         val benchmark2 = BenchmarkCRDummy(name = "Test-Benchmark-123")
         benchmarkResourceList.items = listOf(benchmark1.getCR(), benchmark2.getCR())
 
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
index 51347d41b396bf375c14d5580b0f2619ce5b518c..9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
@@ -3,13 +3,13 @@ package theodolite.execution.operator
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.model.crd.ExecutionCRD
 import theodolite.model.crd.ExecutionStatus
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
 class ExecutionCRDummy(name: String, benchmark: String) {
 
     private val execution = BenchmarkExecution()
     private val executionState = ExecutionStatus()
-    private val executionCR = ExecutionCRD(execution, executionState)
+    private val executionCR = ExecutionCRD()
 
     fun getCR(): ExecutionCRD {
         return this.executionCR
@@ -25,6 +25,7 @@ class ExecutionCRDummy(name: String, benchmark: String) {
         executionCR.metadata.name = name
         executionCR.kind = "Execution"
         executionCR.apiVersion = "v1"
+        executionCR.status = executionState
 
         // configure execution
         val loadType = BenchmarkExecution.LoadDefinition()
@@ -51,6 +52,6 @@ class ExecutionCRDummy(name: String, benchmark: String) {
         execution.configOverrides = mutableListOf()
         execution.name = executionCR.metadata.name
 
-        executionState.executionState = ExecutionStates.PENDING.value
+        executionState.executionState = ExecutionState.PENDING
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
index d8db7ab3b64ce3856984ddbc279ef148aa325e73..c08e0565375de84a228a28b6d68a0b713af97d0f 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
@@ -1,227 +1,264 @@
 package theodolite.execution.operator
 
-import io.fabric8.kubernetes.api.model.KubernetesResource
-import io.fabric8.kubernetes.client.informers.SharedInformerFactory
+import io.fabric8.kubernetes.api.model.KubernetesResourceList
+import io.fabric8.kubernetes.client.dsl.MixedOperation
+import io.fabric8.kubernetes.client.dsl.Resource
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
 import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.*
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
-import theodolite.k8s.K8sManager
-import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
-import theodolite.model.crd.ExecutionStates
-import java.lang.Thread.sleep
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.kotlin.*
+import theodolite.model.crd.ExecutionCRD
+import theodolite.model.crd.ExecutionState
+import java.io.FileInputStream
+import java.util.stream.Stream
+
+// TODO move somewhere else
+typealias ExecutionClient = MixedOperation<ExecutionCRD, KubernetesResourceList<ExecutionCRD>, Resource<ExecutionCRD>>
+
+@WithKubernetesTestServer
+@QuarkusTest
+class ExecutionEventHandlerTest {
 
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
 
-private const val RESYNC_PERIOD = 1000 * 1000.toLong()
+    lateinit var executionClient: ExecutionClient
 
+    lateinit var controller: TheodoliteController
 
-@QuarkusTest
-class ExecutionEventHandlerTest {
-    private final val server = KubernetesServer(false, true)
-    private val testResourcePath = "./src/test/resources/k8s-resource-files/"
-    private final val executionName = "example-execution"
-    lateinit var factory: SharedInformerFactory
-    lateinit var executionVersion1: KubernetesResource
-    lateinit var executionVersion2: KubernetesResource
     lateinit var stateHandler: ExecutionStateHandler
-    lateinit var manager: K8sManager
-    lateinit var controller: TheodoliteController
+
+    lateinit var eventHandler: ExecutionEventHandler
 
     @BeforeEach
     fun setUp() {
         server.before()
-        val operator = TheodoliteOperator()
-        this.controller = operator.getController(
-            client = server.client,
-            executionStateHandler = ExecutionStateHandler(client = server.client),
-            benchmarkStateHandler = BenchmarkStateHandler(client = server.client)
-        )
 
-        this.factory = operator.getExecutionEventHandler(this.controller, server.client)
-        this.stateHandler = TheodoliteOperator().getExecutionStateHandler(client = server.client)
+        this.server.client
+            .apiextensions().v1()
+            .customResourceDefinitions()
+            .load(FileInputStream("crd/crd-execution.yaml"))
+            .create()
 
-        this.executionVersion1 = K8sResourceLoaderFromFile(server.client)
-            .loadK8sResource("Execution", testResourcePath + "test-execution.yaml")
+        this.executionClient = this.server.client.resources(ExecutionCRD::class.java)
 
-        this.executionVersion2 = K8sResourceLoaderFromFile(server.client)
-            .loadK8sResource("Execution", testResourcePath + "test-execution-update.yaml")
-
-        this.stateHandler = operator.getExecutionStateHandler(server.client)
-
-        this.manager = K8sManager((server.client))
+        this.controller = mock()
+        this.stateHandler = ExecutionStateHandler(server.client)
+        this.eventHandler = ExecutionEventHandler(this.controller, this.stateHandler)
     }
 
     @AfterEach
     fun tearDown() {
         server.after()
-        factory.stopAllRegisteredInformers()
     }
 
     @Test
-    @DisplayName("Check namespaced property of informers")
-    fun testNamespaced() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        server.lastRequest
-        // the second request must be namespaced (this is the first `GET` request)
-        assert(
-            server
-                .lastRequest
-                .toString()
-                .contains("namespaces")
-        )
+    fun testCrdRegistered() {
+        val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list();
+        assertEquals(1, crds.items.size)
+        assertEquals("execution", crds.items[0].spec.names.kind)
     }
 
     @Test
-    @DisplayName("Test onAdd method for executions without execution state")
-    fun testWithoutState() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testExecutionDeploy() {
+        getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+
+        val executions = executionClient.list().items
+        assertEquals(1, executions.size)
     }
 
     @Test
-    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
-    fun testWithStateIsRunning() {
-        manager.deploy(executionVersion1)
-        stateHandler
-            .setExecutionState(
-                resourceName = executionName,
-                status = ExecutionStates.RUNNING
-            )
-        factory.startAllRegisteredInformers()
-        sleep(500)
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testStatusSet() {
+        val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+        assertNotNull(execCreated.status)
+        val execResponse = this.executionClient.withName(execCreated.metadata.name)
+        val execResponseItem = execResponse.get()
+        assertNotNull(execResponseItem.status)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `PENDING`")
-    fun testOnUpdatePending() {
-        manager.deploy(executionVersion1)
-
-        factory.startAllRegisteredInformers()
-        sleep(500)
+    @DisplayName("Test onAdd method for executions without execution state")
+    fun testOnAddWithoutStatus() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
 
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+        // Get execution from server
+        val executionResponse = this.executionClient.withName(executionName).get()
+        this.eventHandler.onAdd(executionResponse)
 
-        manager.deploy(executionVersion2)
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `FINISHED`")
-    fun testOnUpdateFinished() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.FINISHED
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
+    fun testOnAddWithStatusRunning() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
+        stateHandler.setExecutionState(executionName, ExecutionState.RUNNING)
+
+        // Update status of execution
+        execution.status.executionState = ExecutionState.RUNNING
+        executionResource.patchStatus(execution)
+
+
+        // Get execution from server
+        val executionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(ExecutionState.RUNNING, this.executionClient.withName(executionName).get().status.executionState)
+
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        this.eventHandler.onAdd(executionResponse)
+
+        verify(this.controller).stop(true)
+        assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `FAILURE`")
-    fun testOnUpdateFailure() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.FAILURE
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    @DisplayName("Test onUpdate method for execution with no status")
+    fun testOnUpdateWithoutStatus() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution at server has no status
+        assertEquals(ExecutionState.NO_STATE, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+
+        this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse)
+
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
     }
 
+    @ParameterizedTest
+    @MethodSource("provideOnUpdateTestArguments")
+    @DisplayName("Test onUpdate method for execution with different status")
+    fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution
+        firstExecution.status.executionState = beforeState
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(beforeState, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+
+        this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse)
+
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState)
+    }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `RUNNING`")
-    fun testOnUpdateRunning() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.RUNNING
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testOnDeleteWithExecutionRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        this.eventHandler.onDelete(firstExecutionResponse, true)
+
+        verify(this.controller).stop(false)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `RESTART`")
-    fun testOnUpdateRestart() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.RESTART
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
+    fun testOnDeleteWithExecutionNotRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false)
+
+        this.eventHandler.onDelete(firstExecutionResponse, true)
+
+        verify(this.controller, never()).stop(false)
+    }
+
+    private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> {
+        return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName))
+    }
+
+    companion object {
+        @JvmStatic
+        fun provideOnUpdateTestArguments(): Stream<Arguments> =
+            Stream.of(
+                // before state -> expected state
+                Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.RUNNING, ExecutionState.RESTART),
+                Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART)
             )
-        )
     }
+
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..adddc705616935e5440c1c601615ce9a065df4c4
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt
@@ -0,0 +1,283 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.client.dsl.Resource
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.kotlin.*
+import theodolite.model.crd.ExecutionCRD
+import theodolite.model.crd.ExecutionState
+import java.io.FileInputStream
+import java.util.concurrent.CountDownLatch
+import java.util.stream.Stream
+
+@WithKubernetesTestServer
+@QuarkusTest
+class ExecutionEventHandlerTestWithInformer {
+
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
+
+    lateinit var executionClient: ExecutionClient
+
+    lateinit var controller: TheodoliteController
+
+    lateinit var stateHandler: ExecutionStateHandler
+
+    lateinit var addCountDownLatch: CountDownLatch
+    lateinit var updateCountDownLatch: CountDownLatch
+    lateinit var deleteCountDownLatch: CountDownLatch
+
+    lateinit var eventHandler: ExecutionEventHandlerWrapper
+
+    @BeforeEach
+    fun setUp() {
+        server.before()
+
+        this.server.client
+            .apiextensions().v1()
+            .customResourceDefinitions()
+            .load(FileInputStream("crd/crd-execution.yaml"))
+            .create()
+
+        this.executionClient = this.server.client.resources(ExecutionCRD::class.java)
+
+        this.controller = mock()
+        this.stateHandler = ExecutionStateHandler(server.client)
+        this.addCountDownLatch = CountDownLatch(1)
+        this.updateCountDownLatch = CountDownLatch(2)
+        this.deleteCountDownLatch = CountDownLatch(1)
+        this.eventHandler = ExecutionEventHandlerWrapper(
+            ExecutionEventHandler(this.controller, this.stateHandler),
+            { addCountDownLatch.countDown() },
+            { updateCountDownLatch.countDown() },
+            { deleteCountDownLatch.countDown() }
+        )
+    }
+
+    @AfterEach
+    fun tearDown() {
+        server.after()
+        this.server.client.informers().stopAllRegisteredInformers()
+    }
+
+    @Test
+    fun testCrdRegistered() {
+        val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list();
+        assertEquals(1, crds.items.size)
+        assertEquals("execution", crds.items[0].spec.names.kind)
+    }
+
+    @Test
+    fun testExecutionDeploy() {
+        getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+
+        val executions = executionClient.list().items
+        assertEquals(1, executions.size)
+    }
+
+    @Test
+    fun testStatusSet() {
+        val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+        assertNotNull(execCreated.status)
+        val execResponse = this.executionClient.withName(execCreated.metadata.name)
+        val execResponseItem = execResponse.get()
+        assertNotNull(execResponseItem.status)
+    }
+
+    @Test
+    @DisplayName("Test onAdd method for executions without execution state")
+    fun testOnAddWithoutStatus() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Await informer called
+        this.addCountDownLatch.await()
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
+    @Disabled("Flaky test due to multiple informer events.")
+    fun testOnAddWithStatusRunning() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val executionName = executionResource.get().metadata.name
+
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        val execution = executionResource.create()
+
+        // Update status of execution
+        execution.status.executionState = ExecutionState.RUNNING
+        executionResource.patchStatus(execution)
+
+        // Assert that status at server matches set status
+        // assertEquals(ExecutionStates.RUNNING, this.executionClient.withName(executionName).get().status.executionState)
+
+        // Await informer called
+        this.addCountDownLatch.await()
+        verify(this.controller).stop(true)
+        assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @DisplayName("Test onUpdate method for execution with no status")
+    fun testOnUpdateWithoutStatus() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution at server has pending status
+        assertEquals(ExecutionState.PENDING, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+
+        // Await informer called
+        this.updateCountDownLatch.await()
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideOnUpdateTestArguments")
+    @DisplayName("Test onUpdate method for execution with different status")
+    fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution
+        firstExecution.status.executionState = beforeState
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(beforeState, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+
+        // Await informer called
+        this.updateCountDownLatch.await()
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @Disabled("Informer also called onAdd and changes status")
+    fun testOnDeleteWithExecutionRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution deleted at server
+        assertNull(secondExecutionResponse)
+
+        // Await informer called
+        this.deleteCountDownLatch.await()
+
+        verify(this.controller).stop(false)
+    }
+
+    @Test
+    fun testOnDeleteWithExecutionNotRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // Await informer called
+        this.deleteCountDownLatch.await()
+
+        verify(this.controller, never()).stop(false)
+    }
+
+    private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> {
+        return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName))
+    }
+
+    companion object {
+        @JvmStatic
+        fun provideOnUpdateTestArguments(): Stream<Arguments> =
+            Stream.of(
+                // before state -> expected state
+                Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING),
+                // Arguments.of(ExecutionStates.RUNNING, ExecutionStates.RESTART), // see testOnDeleteWithExecutionRunning
+                Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART)
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5dbc515a7799dd51e6395153f13d80650587d7fa
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt
@@ -0,0 +1,27 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.client.informers.ResourceEventHandler
+import theodolite.model.crd.ExecutionCRD
+
+class ExecutionEventHandlerWrapper(
+    private val executionEventHandler: ExecutionEventHandler,
+    private val afterOnAddCallback: () -> Unit,
+    private val afterOnUpdateCallback: () -> Unit,
+    private val afterOnDeleteCallback: () -> Unit
+) : ResourceEventHandler<ExecutionCRD> {
+
+    override fun onAdd(execution: ExecutionCRD) {
+        this.executionEventHandler.onAdd(execution)
+        this.afterOnAddCallback()
+    }
+
+    override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) {
+        this.executionEventHandler.onUpdate(oldExecution, newExecution)
+        this.afterOnUpdateCallback()
+    }
+
+    override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) {
+        this.executionEventHandler.onDelete(execution, deletedFinalStateUnknown)
+        this.afterOnDeleteCallback()
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
index a54f4ed6db559f8f7f15ae82deecf3fedf8b4abe..138f79eadc6bdee17e62cc7a961eb7de539fa3df 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
@@ -1,6 +1,9 @@
 package theodolite.execution.operator
 
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertTrue
@@ -9,12 +12,16 @@ import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
 import theodolite.k8s.K8sManager
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 import java.time.Duration
 
+@QuarkusTest
+@WithKubernetesTestServer
 class StateHandlerTest {
     private val testResourcePath = "./src/test/resources/k8s-resource-files/"
-    private val server = KubernetesServer(false, true)
+
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
 
     @BeforeEach
     fun setUp() {
@@ -47,14 +54,7 @@ class StateHandlerTest {
     @DisplayName("Test empty execution state")
     fun executionWithoutExecutionStatusTest() {
         val handler = ExecutionStateHandler(client = server.client)
-        assertEquals(ExecutionStates.NO_STATE, handler.getExecutionState("example-execution"))
-    }
-
-    @Test
-    @DisplayName("Test empty duration state")
-    fun executionWithoutDurationStatusTest() {
-        val handler = ExecutionStateHandler(client = server.client)
-        assertEquals("-", handler.getDurationState("example-execution"))
+        assertEquals(ExecutionState.NO_STATE, handler.getExecutionState("example-execution"))
     }
 
     @Test
@@ -62,16 +62,8 @@ class StateHandlerTest {
     fun executionStatusTest() {
         val handler = ExecutionStateHandler(client = server.client)
 
-        assertTrue(handler.setExecutionState("example-execution", ExecutionStates.INTERRUPTED))
-        assertEquals(ExecutionStates.INTERRUPTED, handler.getExecutionState("example-execution"))
+        assertTrue(handler.setExecutionState("example-execution", ExecutionState.INTERRUPTED))
+        assertEquals(ExecutionState.INTERRUPTED, handler.getExecutionState("example-execution"))
     }
 
-    @Test
-    @DisplayName("Test set and get of the duration state")
-    fun durationStatusTest() {
-        val handler = ExecutionStateHandler(client = server.client)
-
-        assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100)))
-        assertEquals("0s", handler.getDurationState("example-execution"))
-    }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
index 7c69618de03f730f5b6f1cb83c5df544e2cd120c..ffc3f2f2b8083ab8b8170fa77c19de3a6ef387e7 100644
--- a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
@@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
-import mu.KotlinLogging
 import org.json.JSONObject
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Assertions.assertEquals
@@ -17,9 +16,6 @@ import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 
-
-private val logger = KotlinLogging.logger {}
-
 @QuarkusTest
 @JsonIgnoreProperties(ignoreUnknown = true)
 class K8sManagerTest {
diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..157bc1c03cc40375c928677189f549052e1e134d
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt
@@ -0,0 +1,144 @@
+package theodolite.model.crd
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.exc.InvalidFormatException
+import io.fabric8.kubernetes.api.model.MicroTime
+import io.fabric8.kubernetes.api.model.Duration as K8sDuration
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import java.time.Clock
+import java.time.Duration
+import java.time.Instant
+import java.time.ZoneId
+
+
+internal class ExecutionStatusTest {
+
+    @Test
+    fun testDefaultStateSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionState")
+        assertTrue(jsonField.isTextual)
+        assertEquals(ExecutionState.NO_STATE.value, json.get("executionState").asText())
+    }
+
+    @Test
+    fun testCustomStateSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        executionStatus.executionState = ExecutionState.PENDING
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionState")
+        assertTrue(jsonField.isTextual)
+        assertEquals(ExecutionState.PENDING.value, json.get("executionState").asText())
+    }
+
+    @Test
+    fun testStateDeserialization() {
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", ExecutionState.RUNNING.value)
+        json.put("executionDuration", "")
+        val jsonString = objectMapper.writeValueAsString(json)
+        val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        val executionState =  executionStatus.executionState
+        assertNotNull(executionState)
+        assertEquals(ExecutionState.RUNNING, executionState)
+    }
+
+    @Test
+    fun testInvalidStateDeserialization() {
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", "invalid-state")
+        json.put("executionDuration", "")
+        val jsonString = objectMapper.writeValueAsString(json)
+        assertThrows<InvalidFormatException> {
+            objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        }
+    }
+
+    @Test
+    fun `test duration for no start and completion time`() {
+        val executionStatus = ExecutionStatus()
+        assertNull(executionStatus.startTime)
+        assertNull(executionStatus.completionTime)
+        assertNull(executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for no start but completion time`() {
+        val executionStatus = ExecutionStatus()
+        executionStatus.completionTime = MicroTime(Instant.parse("2022-01-02T18:59:20.492103Z").toString())
+        assertNull(executionStatus.startTime)
+        assertNull(executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for non completed execution`() {
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        val sinceStart = Duration.ofMinutes(5)
+        val executionStatus = ExecutionStatus(clock = Clock.fixed(startInstant.plus(sinceStart), ZoneId.systemDefault()))
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for completed execution`() {
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        val sinceStart = Duration.ofMinutes(5)
+        val executionStatus = ExecutionStatus()
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        executionStatus.completionTime = MicroTime(startInstant.plus(sinceStart).toString())
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration)
+    }
+
+    @Test
+    fun testDurationSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        executionStatus.completionTime = MicroTime(startInstant.plus(Duration.ofMinutes(15)).toString())
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionDuration")
+        assertTrue(jsonField.isTextual)
+        assertEquals("15m", jsonField.asText())
+    }
+
+    @Test
+    fun testNotStartedDurationSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+
+        assertTrue(json.get("startTime").isNull)
+        assertTrue(json.get("completionTime").isNull)
+        assertTrue(json.get("executionDuration").isNull)
+    }
+
+    @Test
+    fun testWrongDurationDeserialization() {
+        val startTime = "2022-01-02T18:59:20.492103Z"
+        val completionTime = "2022-01-02T19:14:20.492103Z"
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", ExecutionState.RUNNING.value)
+        json.put("executionDuration", "20m")
+        json.put("startTime", startTime)
+        json.put("completionTime", completionTime)
+        val jsonString = objectMapper.writeValueAsString(json)
+        val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(Duration.ofMinutes(15), executionStatus.executionDuration?.duration)
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt
index 7332e53f9e1814f28b8ff37a595b31b0eb931ea7..ae80312afd2c128f0f542306a8ffda7f3f53876b 100644
--- a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt
@@ -4,7 +4,7 @@ import io.quarkus.test.junit.QuarkusTest
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
 import theodolite.execution.operator.ExecutionCRDummy
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
 
 @QuarkusTest
@@ -12,14 +12,13 @@ class ExecutionStateComparatorTest {
 
     @Test
     fun testCompare() {
-        val comparator = ExecutionStateComparator(ExecutionStates.RESTART)
+        val comparator = ExecutionStateComparator(ExecutionState.RESTART)
         val execution1 = ExecutionCRDummy("dummy1", "default-benchmark")
         val execution2 = ExecutionCRDummy("dummy2", "default-benchmark")
-        execution1.getStatus().executionState = ExecutionStates.RESTART.value
-        execution2.getStatus().executionState = ExecutionStates.PENDING.value
+        execution1.getStatus().executionState = ExecutionState.RESTART
+        execution2.getStatus().executionState = ExecutionState.PENDING
         val list = listOf(execution2.getCR(), execution1.getCR())
 
-
         assertEquals(
             list.reversed(),
             list.sortedWith(comparator)
diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
index 6b8aa1d567fd2c93c1301fe3f953273e0f5d5420..f84536bfc029a829c1798293938386965eedcf47 100644
--- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
@@ -96,7 +96,7 @@ internal class IOHandlerTest {
     }
 
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "./src/test/resources"),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false")
@@ -105,7 +105,7 @@ internal class IOHandlerTest {
         assertEquals("./src/test/resources/", IOHandler().getResultFolderURL())
     }
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "$FOLDER_URL-0"),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false")
@@ -121,7 +121,7 @@ internal class IOHandlerTest {
         assertTrue(exceptionWasThrown)
     }
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = FOLDER_URL),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true")
@@ -130,7 +130,7 @@ internal class IOHandlerTest {
         assertEquals("$FOLDER_URL/", IOHandler().getResultFolderURL())
     }
 
-    @Test()
+    @Test
     @ClearEnvironmentVariable(key = "RESULTS_FOLDER")
     @SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true")
     fun testGetResultFolderURL_CreateFolderButNoFolderGiven() {
diff --git a/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..1f0955d450f0dc49ca715b1a0a88a5aa746ee11e
--- /dev/null
+++ b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline