diff --git a/doc/source/jobs.rst b/doc/source/jobs.rst
index 073ba5325..2c5c56742 100644
--- a/doc/source/jobs.rst
+++ b/doc/source/jobs.rst
@@ -10,3 +10,4 @@ Jobs
    docker-jobs
    go-jobs
    helm-jobs
+   system-jobs
diff --git a/doc/source/roles.rst b/doc/source/roles.rst
index 3c2277cca..4139b7749 100644
--- a/doc/source/roles.rst
+++ b/doc/source/roles.rst
@@ -22,4 +22,5 @@ Roles
    launchpad-roles
    puppet-roles
    python-roles
+   system-roles
    translation-roles
diff --git a/doc/source/system-jobs.rst b/doc/source/system-jobs.rst
new file mode 100644
index 000000000..2a9aaefb7
--- /dev/null
+++ b/doc/source/system-jobs.rst
@@ -0,0 +1,5 @@
+System Jobs
+===========
+
+.. zuul:autojob:: phoronix-test-suite
+.. zuul:autojob:: phoronix-combine-results
diff --git a/doc/source/system-roles.rst b/doc/source/system-roles.rst
new file mode 100644
index 000000000..828b30dc9
--- /dev/null
+++ b/doc/source/system-roles.rst
@@ -0,0 +1,7 @@
+System Roles
+============
+
+.. zuul:autorole:: ensure-phoronix-test-suite
+.. zuul:autorole:: phoronix-test-suite
+.. zuul:autorole:: phoronix-combine-results
+.. zuul:autorole:: fetch-phoronix-results
diff --git a/playbooks/phoronix-test-suite/combine.yaml b/playbooks/phoronix-test-suite/combine.yaml
new file mode 100644
index 000000000..e23c3f336
--- /dev/null
+++ b/playbooks/phoronix-test-suite/combine.yaml
@@ -0,0 +1,5 @@
+---
+- hosts: all
+  roles:
+    - ensure-phoronix-test-suite
+    - phoronix-combine-results
diff --git a/playbooks/phoronix-test-suite/post-combine.yaml b/playbooks/phoronix-test-suite/post-combine.yaml
new file mode 100644
index 000000000..ecb5f829c
--- /dev/null
+++ b/playbooks/phoronix-test-suite/post-combine.yaml
@@ -0,0 +1,4 @@
+---
+- hosts: all
+  roles:
+    - fetch-phoronix-results
diff --git a/playbooks/phoronix-test-suite/post.yaml b/playbooks/phoronix-test-suite/post.yaml
new file mode 100644
index 000000000..ecb5f829c
--- /dev/null
+++ b/playbooks/phoronix-test-suite/post.yaml
@@ -0,0 +1,4 @@
+---
+- hosts: all
+  roles:
+    - fetch-phoronix-results
diff --git a/playbooks/phoronix-test-suite/run.yaml b/playbooks/phoronix-test-suite/run.yaml
new file mode 100644
index 000000000..40961f60e
--- /dev/null
+++ b/playbooks/phoronix-test-suite/run.yaml
@@ -0,0 +1,4 @@
+- hosts: all
+  roles:
+    - ensure-phoronix-test-suite
+    - phoronix-test-suite
diff --git a/roles/ensure-phoronix-test-suite/README.rst b/roles/ensure-phoronix-test-suite/README.rst
new file mode 100644
index 000000000..a1bac07a8
--- /dev/null
+++ b/roles/ensure-phoronix-test-suite/README.rst
@@ -0,0 +1,5 @@
+Install phoronix-test-suite
+
+This role requires a phoronix-test-suite package, provided by these distros:
+
+- Fedora
diff --git a/roles/ensure-phoronix-test-suite/files/user-config.xml b/roles/ensure-phoronix-test-suite/files/user-config.xml
new file mode 100644
index 000000000..5805c3399
--- /dev/null
+++ b/roles/ensure-phoronix-test-suite/files/user-config.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!--Phoronix Test Suite v8.6.1-->
+<?xml-stylesheet type="text/xsl" href="xsl/pts-user-config-viewer.xsl"?>
+<PhoronixTestSuite>
+  <Options>
+    <OpenBenchmarking>
+      <AnonymousUsageReporting>FALSE</AnonymousUsageReporting>
+      <IndexCacheTTL>3</IndexCacheTTL>
+      <AlwaysUploadSystemLogs>FALSE</AlwaysUploadSystemLogs>
+    </OpenBenchmarking>
+    <General>
+      <DefaultBrowser></DefaultBrowser>
+      <UsePhodeviCache>TRUE</UsePhodeviCache>
+      <DefaultDisplayMode>DEFAULT</DefaultDisplayMode>
+      <PhoromaticServers></PhoromaticServers>
+      <FullOutput>FALSE</FullOutput>
+      <ColoredConsole>AUTO</ColoredConsole>
+    </General>
+    <Modules>
+      <LoadModules>toggle_screensaver, update_checker, perf_tips, ob_auto_compare</LoadModules>
+    </Modules>
+    <Installation>
+      <RemoveDownloadFiles>FALSE</RemoveDownloadFiles>
+      <SearchMediaForCache>TRUE</SearchMediaForCache>
+      <SymLinkFilesFromCache>FALSE</SymLinkFilesFromCache>
+      <PromptForDownloadMirror>FALSE</PromptForDownloadMirror>
+      <EnvironmentDirectory>~/.phoronix-test-suite/installed-tests/</EnvironmentDirectory>
+      <CacheDirectory>~/.phoronix-test-suite/download-cache/</CacheDirectory>
+    </Installation>
+    <Testing>
+      <SaveSystemLogs>TRUE</SaveSystemLogs>
+      <SaveInstallationLogs>FALSE</SaveInstallationLogs>
+      <SaveTestLogs>FALSE</SaveTestLogs>
+      <RemoveTestInstallOnCompletion></RemoveTestInstallOnCompletion>
+      <ResultsDirectory>~/test-results/</ResultsDirectory>
+      <AlwaysUploadResultsToOpenBenchmarking>FALSE</AlwaysUploadResultsToOpenBenchmarking>
+      <AutoSortRunQueue>TRUE</AutoSortRunQueue>
+      <ShowPostRunStatistics>TRUE</ShowPostRunStatistics>
+    </Testing>
+    <TestResultValidation>
+      <DynamicRunCount>TRUE</DynamicRunCount>
+      <LimitDynamicToTestLength>20</LimitDynamicToTestLength>
+      <StandardDeviationThreshold>3.50</StandardDeviationThreshold>
+      <ExportResultsTo></ExportResultsTo>
+      <MinimalTestTime>2</MinimalTestTime>
+      <DropNoisyResults>FALSE</DropNoisyResults>
+    </TestResultValidation>
+    <BatchMode>
+      <SaveResults>TRUE</SaveResults>
+      <OpenBrowser>FALSE</OpenBrowser>
+      <UploadResults>FALSE</UploadResults>
+      <PromptForTestIdentifier>FALSE</PromptForTestIdentifier>
+      <PromptForTestDescription>FALSE</PromptForTestDescription>
+      <PromptSaveName>FALSE</PromptSaveName>
+      <RunAllTestCombinations>TRUE</RunAllTestCombinations>
+      <Configured>TRUE</Configured>
+    </BatchMode>
+    <Networking>
+      <NoInternetCommunication>FALSE</NoInternetCommunication>
+      <NoNetworkCommunication>FALSE</NoNetworkCommunication>
+      <Timeout>20</Timeout>
+      <ProxyAddress></ProxyAddress>
+      <ProxyPort></ProxyPort>
+      <ProxyUser></ProxyUser>
+      <ProxyPassword></ProxyPassword>
+    </Networking>
+    <Server>
+      <RemoteAccessPort>RANDOM</RemoteAccessPort>
+      <Password></Password>
+      <WebSocketPort>RANDOM</WebSocketPort>
+      <AdvertiseServiceZeroConf>TRUE</AdvertiseServiceZeroConf>
+      <AdvertiseServiceOpenBenchmarkRelay>TRUE</AdvertiseServiceOpenBenchmarkRelay>
+      <PhoromaticStorage>~/.phoronix-test-suite/phoromatic/</PhoromaticStorage>
+    </Server>
+  </Options>
+</PhoronixTestSuite>
diff --git a/roles/ensure-phoronix-test-suite/tasks/main.yaml b/roles/ensure-phoronix-test-suite/tasks/main.yaml
new file mode 100644
index 000000000..e38360f6e
--- /dev/null
+++ b/roles/ensure-phoronix-test-suite/tasks/main.yaml
@@ -0,0 +1,25 @@
+---
+- name: Install phoronix-test-suite
+  become: yes
+  package:
+    name: phoronix-test-suite
+
+- name: Create config directory
+  file:
+    path: "{{ ansible_env.HOME }}/.phoronix-test-suite"
+    state: directory
+
+- name: Install config
+  copy:
+    src: user-config.xml
+    dest: "{{ ansible_env.HOME }}/.phoronix-test-suite/user-config.xml"
+
+- name: Ensure test results are cleaned
+  file:
+    path: "{{ ansible_env.HOME }}/test-results"
+    state: absent
+
+- name: Create result directory
+  file:
+    path: "{{ ansible_env.HOME }}/test-results/"
+    state: directory
diff --git a/roles/fetch-phoronix-results/README.rst b/roles/fetch-phoronix-results/README.rst
new file mode 100644
index 000000000..e7311df84
--- /dev/null
+++ b/roles/fetch-phoronix-results/README.rst
@@ -0,0 +1,5 @@
+Collect output from a phoronix-test-suite run
+
+The role copies the output from a phoronix-test-suite run
+on the worker to the log root of the executor using a
+directory named after the ansible_hostname.
diff --git a/roles/fetch-phoronix-results/tasks/main.yaml b/roles/fetch-phoronix-results/tasks/main.yaml
new file mode 100644
index 000000000..40afd5b60
--- /dev/null
+++ b/roles/fetch-phoronix-results/tasks/main.yaml
@@ -0,0 +1,29 @@
+---
+- name: Set phoronix log path
+  set_fact:
+    log_path: "{{ zuul.executor.log_root }}/"
+  when: log_path is not defined
+
+- name: Discover the last result path
+  shell: |
+    set -o pipefail
+    ls -td ~/test-results/* | head -n 1
+  args:
+    executable: /bin/bash
+  register: result_path
+
+- name: Collect phoronix result
+  synchronize:
+    src: "{{ result_path.stdout }}/"
+    dest: "{{ log_path }}/phoronix-results-{{ ansible_hostname }}/"
+    mode: pull
+
+- name: Return site artifact location to Zuul
+  zuul_return:
+    data:
+      zuul:
+        artifacts:
+          - name: "Phoronix test result"
+            metadata:
+              type: phoronix_result
+            url: "phoronix-results-{{ ansible_hostname }}/"
diff --git a/roles/phoronix-combine-results/README.rst b/roles/phoronix-combine-results/README.rst
new file mode 100644
index 000000000..f8024fdac
--- /dev/null
+++ b/roles/phoronix-combine-results/README.rst
@@ -0,0 +1 @@
+Combine phoronix-test-suite results
diff --git a/roles/phoronix-combine-results/tasks/fetch-result.yaml b/roles/phoronix-combine-results/tasks/fetch-result.yaml
new file mode 100644
index 000000000..75a3d6344
--- /dev/null
+++ b/roles/phoronix-combine-results/tasks/fetch-result.yaml
@@ -0,0 +1,10 @@
+---
+- name: Create result dir
+  file:
+    path: "{{ ansible_env.HOME }}/test-results/{{ item.url.rstrip('/')|basename }}"
+    state: directory
+
+- name: Fetch artifact
+  get_url:
+    url: "{{ item.url }}/composite.xml"
+    dest: "{{ ansible_env.HOME }}/test-results/{{ item.url.rstrip('/')|basename }}/composite.xml"
diff --git a/roles/phoronix-combine-results/tasks/main.yaml b/roles/phoronix-combine-results/tasks/main.yaml
new file mode 100644
index 000000000..7ee11ee2a
--- /dev/null
+++ b/roles/phoronix-combine-results/tasks/main.yaml
@@ -0,0 +1,19 @@
+---
+- name: Fetch previous result
+  include_tasks: fetch-result.yaml
+  when:
+    - item.metadata is defined
+    - item.metadata.type is defined
+    - item.metadata.type == "phoronix_result"
+  loop: "{{ zuul.artifacts }}"
+
+- name: List previous result name
+  command: "ls {{ ansible_env.HOME }}/test-results/"
+  register: previous_results
+
+- name: Merge result
+  shell: |
+    set -o pipefail
+    yes | phoronix-test-suite merge-results {{ ' '.join(previous_results.stdout_lines) }}
+  args:
+    executable: /bin/bash
diff --git a/roles/phoronix-test-suite/README.rst b/roles/phoronix-test-suite/README.rst
new file mode 100644
index 000000000..71051a713
--- /dev/null
+++ b/roles/phoronix-test-suite/README.rst
@@ -0,0 +1,8 @@
+Runs phoronix-test-suite
+
+**Role Variables**
+
+.. zuul:rolevar:: phoronix_test_suites
+   :default: ["pts/sysbench"]
+
+   The list of tests to run.
diff --git a/roles/phoronix-test-suite/defaults/main.yaml b/roles/phoronix-test-suite/defaults/main.yaml
new file mode 100644
index 000000000..6862b391b
--- /dev/null
+++ b/roles/phoronix-test-suite/defaults/main.yaml
@@ -0,0 +1,2 @@
+phoronix_test_suites:
+  - pts/sysbench
diff --git a/roles/phoronix-test-suite/tasks/main.yaml b/roles/phoronix-test-suite/tasks/main.yaml
new file mode 100644
index 000000000..e6de51932
--- /dev/null
+++ b/roles/phoronix-test-suite/tasks/main.yaml
@@ -0,0 +1,16 @@
+---
+- name: Install test
+  shell: |
+    set -o pipefail
+    yes | phoronix-test-suite install {{ ' '.join(phoronix_test_suites) }}
+  args:
+    executable: /bin/bash
+  register: _phoronix_cmd
+  failed_when: _phoronix_cmd.rc != 141
+
+- name: Run test
+  shell: |
+    set -o pipefail
+    yes | phoronix-test-suite batch-run {{ ' '.join(phoronix_test_suites) }}
+  register: _phoronix_cmd
+  failed_when: _phoronix_cmd.rc != 141
diff --git a/zuul.d/system-jobs.yaml b/zuul.d/system-jobs.yaml
new file mode 100644
index 000000000..e04450a8a
--- /dev/null
+++ b/zuul.d/system-jobs.yaml
@@ -0,0 +1,13 @@
+- job:
+    name: phoronix-test-suite
+    description: A system job to validate test resources
+    run: playbooks/phoronix-test-suite/run.yaml
+    post-run: playbooks/phoronix-test-suite/post.yaml
+
+- job:
+    name: phoronix-combine-results
+    description: A system job to combine multiple results
+    vars:
+      phoronix_combined_result_name: "phoronix-combined-result"
+    run: playbooks/phoronix-test-suite/combine.yaml
+    post-run: playbooks/phoronix-test-suite/post-combine.yaml