Skip to content

Instantly share code, notes, and snippets.

@robcxyz
Created January 12, 2024 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robcxyz/d00ac0f857d20177f3eec605942cf593 to your computer and use it in GitHub Desktop.
Save robcxyz/d00ac0f857d20177f3eec605942cf593 to your computer and use it in GitHub Desktop.
Ansible playbook to setup raid, run a series of fio tests, aggregate their results in a csv, and then delete the volumes

Adjust this based on your drives

vars/drives.yml

mountpoint: "/data"
volume_name: md1
raid_level: '0'

drives:
  - '/dev/nvme0n1'
  - '/dev/nvme1n1'
  - '/dev/nvme2n1'
  - '/dev/nvme3n1'
  - '/dev/nvme6n1'
  - '/dev/nvme7n1'
  - '/dev/nvme8n1'
  - '/dev/nvme9n1'
  - '/dev/nvme10n1'
  - '/dev/nvme11n1'
  - '/dev/nvme12n1'
  - '/dev/nvme13n1'
---
- name: This play sets up VM volumes and
  hosts: all
  become: true
  vars_files:
    - vars/drives.yml
  tasks:
    - name: Create mount point
      ansible.builtin.file:
        path: '{{mountpoint}}'
        state: directory
        mode: '0755'

    - name: Configure raid
      include_role:
        name: mrlesmithjr.mdadm
      vars:
        mdadm_arrays:
          - name: '{{volume_name}}'
            devices: '{{drives}}'
#            filesystem: 'ext4'
            filesystem_opts: ''
            level: '{{raid_level}}'
            mountpoint: '{{mountpoint}}'
            state: 'present'
            opts: 'noatime'

    - name: Create filesystem on RAID array
      ansible.builtin.command:
        cmd: mkfs.ext4 /dev/{{volume_name}}

    - name: Mount {{volume_name}} to /data
      ansible.builtin.mount:
        path: '{{mountpoint}}'
        src: '/dev/{{volume_name}}'
        fstype: ext4
        state: mounted
        opts: defaults

benchmark.yml

---
- name: Install fio and run benchmarks
  hosts: all
  become: true
  tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Install fio
      ansible.builtin.apt:
        name: fio
        state: present

    - name: Clear existing fio results files from /tmp
      ansible.builtin.file:
        path: "/tmp/fio_results_*"
        state: absent

    - name: Run 4K Random Read/Write Test on /data with different parameters
      ansible.builtin.command:
        cmd: >
          fio --name=rand{{ item.operation }}
          --directory=/data
          --ioengine=libaio
          --iodepth={{ item.iodepth }}
          --rw=rand{{ item.operation }}
          --bs=4k
          --direct=1
          --size=4G
          --numjobs={{ item.numjobs }}
          --runtime=60
          --group_reporting
      loop:
        - { operation: 'read', iodepth: 16, numjobs: 32 }
        - { operation: 'read', iodepth: 16, numjobs: 64 }
        - { operation: 'read', iodepth: 16, numjobs: 128 }
        - { operation: 'read', iodepth: 64, numjobs: 32 }
        - { operation: 'read', iodepth: 64, numjobs: 64 }
        - { operation: 'read', iodepth: 64, numjobs: 128 }
        - { operation: 'read', iodepth: 256, numjobs: 32 }
        - { operation: 'read', iodepth: 256, numjobs: 64 }
        - { operation: 'read', iodepth: 256, numjobs: 128 }
        - { operation: 'write', iodepth: 16, numjobs: 32 }
        - { operation: 'write', iodepth: 16, numjobs: 64 }
        - { operation: 'write', iodepth: 16, numjobs: 128 }
        - { operation: 'write', iodepth: 64, numjobs: 32 }
        - { operation: 'write', iodepth: 64, numjobs: 64 }
        - { operation: 'write', iodepth: 64, numjobs: 128 }
        - { operation: 'write', iodepth: 256, numjobs: 32 }
        - { operation: 'write', iodepth: 256, numjobs: 64 }
        - { operation: 'write', iodepth: 256, numjobs: 128 }
      register: fio_test_result
      loop_control:
        label: "{{ item.operation }}_iodepth_{{ item.iodepth }}_numjobs_{{ item.numjobs }}"

    - name: Save Test Results
      ansible.builtin.copy:
        content: "{{ item.stdout }}"
        dest: "/tmp/fio_results_4k_rand_{{ item.item.operation }}_{{ item.item.iodepth }}_{{ item.item.numjobs }}.txt"
      loop: "{{ fio_test_result.results }}"
      loop_control:
        label: "{{ item.item.operation }}_iodepth_{{ item.item.iodepth }}_numjobs_{{ item.item.numjobs }}"

    - name: Create Python script for processing results
      ansible.builtin.copy:
        dest: "/tmp/process_fio_results.py"
        content: |
          import re
          import csv
          import os

          output_dir = "/tmp"
          pattern = r"iops\s+:\s+min=\d+,\s+max=\d+,\s+avg=(\d+.\d+),"
          csv_file_path = '/tmp/fio_results.csv'

          # Prepare CSV file
          with open(csv_file_path, 'w', newline='') as csvfile:
              csvwriter = csv.writer(csvfile)
              csvwriter.writerow(['Filename', 'Operation', 'Iodepth', 'Numjobs', 'Avg IOPS'])

              for filename in os.listdir(output_dir):
                  if filename.startswith("fio_results_4k_rand") and filename.endswith(".txt"):
                      operation, iodepth, numjobs = filename.replace('fio_results_4k_rand_', '').replace('.txt', '').split('_')
                      filepath = os.path.join(output_dir, filename)
                      with open(filepath, 'r') as file:
                          content = file.read()
                          match = re.search(pattern, content, re.MULTILINE)
                          if match:
                              avg_iops = float(match.group(1))
                              csvwriter.writerow([filename, operation, iodepth, numjobs, avg_iops])

          print(f"Results written to {csv_file_path}")

        mode: '0755'

    - name: Run Python script to process results
      ansible.builtin.command:
        cmd: python3 /tmp/process_fio_results.py

    - name: Fetch results JSON file to localhost
      ansible.builtin.fetch:
        src: "/tmp/fio_results.csv"
        dest: "./fio_results.csv"
        flat: yes

cleanup.yml

---
- name: Dismantle and delete RAID array md1
  hosts: all
  become: true
  vars_files:
    - vars/drives.yml
  tasks:

    - name: Unmount the RAID array if it's mounted
      ansible.builtin.mount:
        path: "{{mountpoint}}"
        src: "/dev/{{volume_name}}"
        fstype: ext4
        state: unmounted
      ignore_errors: yes

    - name: Stop the RAID array md1
      ansible.builtin.command:
        cmd: mdadm --stop /dev/{{volume_name}}
      ignore_errors: yes

    - name: Wipe NVMe drives
      ansible.builtin.shell:
        cmd: "wipefs -a {{ item }}"
      ignore_errors: yes
      loop: "{{drives}}"

    - name: Zero superblock on RAID devices
      ansible.builtin.shell:
        cmd: mdadm --zero-superblock {{ item }}
      ignore_errors: yes
      loop: "{{drives}}"

    - name: Remove or comment RAID array from fstab
      ansible.builtin.lineinfile:
        path: /etc/fstab
        regexp: '^/dev/{{volume_name}}'
        state: absent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment