#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import os
import sys

import lib.base
import lib.disk
from lib.globals import STATE_OK, STATE_UNKNOWN, STATE_WARN

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026040801'

DESCRIPTION = """Reports internal XFS filesystem statistics from /proc/fs/xfs/stat. Useful for
understanding I/O characteristics and identifying performance bottlenecks on XFS
volumes."""


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    args, _ = parser.parse_known_args()
    return args


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    if not os.path.exists('/proc/fs/xfs/stat'):
        lib.base.cu('No mounted XFS filesystem found.')

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''

    # analyze data
    xfs_stat = lib.base.coe(lib.disk.read_file('/proc/fs/xfs/stat'))
    for row in xfs_stat.strip().split('\n'):
        stat = row.split(' ')

        # ExtentAllocationStats
        if stat[0] == 'extent_alloc':
            if len(stat) != 5:
                lib.base.oao(
                    'Incorrect number of values for XFS extent allocation stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            extents_allocated = stat[1]
            blocks_allocated = stat[2]
            extents_freed = stat[3]
            blocks_freed = stat[4]
            perfdata += lib.base.get_perfdata(
                'extent_allocation_blocks_allocated_total',
                blocks_allocated,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'extent_allocation_blocks_freed_total',
                blocks_freed,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'extent_allocation_extents_allocated_total',
                extents_allocated,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'extent_allocation_extents_freed_total',
                extents_freed,
                uom='c',
                _min=0,
            )

        # BTreeStats
        if stat[0] == 'abt':
            if len(stat) != 5:
                lib.base.oao(
                    'Incorrect number of values for XFS btree stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            lookups = stat[1]
            compares = stat[2]
            records_inserted = stat[3]
            records_deleted = stat[4]
            perfdata += lib.base.get_perfdata(
                'allocation_btree_compares_total',
                compares,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'allocation_btree_lookups_total',
                lookups,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'allocation_btree_records_deleted_total',
                records_deleted,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'allocation_btree_records_inserted_total',
                records_inserted,
                uom='c',
                _min=0,
            )

        # BlockMappingStats
        if stat[0] == 'blk_map':
            if len(stat) != 8:
                lib.base.oao(
                    'Incorrect number of values for XFS block mapping stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            reads = stat[1]
            writes = stat[2]
            unmaps = stat[3]
            extent_list_insertions = stat[4]
            extent_list_deletions = stat[5]
            extent_list_lookups = stat[6]
            extent_list_compares = stat[7]
            perfdata += lib.base.get_perfdata(
                'block_mapping_extent_list_compares_total',
                extent_list_compares,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_extent_list_deletions_total',
                extent_list_deletions,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_extent_list_insertions_total',
                extent_list_insertions,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_extent_list_lookups_total',
                extent_list_lookups,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_reads_total',
                reads,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_unmaps_total',
                unmaps,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_mapping_writes_total',
                writes,
                uom='c',
                _min=0,
            )

        # BTreeStats
        if stat[0] == 'bmbt':
            if len(stat) != 5:
                lib.base.oao(
                    'Incorrect number of values for XFS btree stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            lookups = stat[1]
            compares = stat[2]
            recordsinserted = stat[3]
            recordsdeleted = stat[4]
            perfdata += lib.base.get_perfdata(
                'block_map_btree_compares_total',
                compares,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_map_btree_lookups_total',
                lookups,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_map_btree_records_deleted_total',
                recordsdeleted,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'block_map_btree_records_inserted_total',
                recordsinserted,
                uom='c',
                _min=0,
            )

        # DirectoryOperationStats
        if stat[0] == 'dir':
            if len(stat) != 5:
                lib.base.oao(
                    'Incorrect number of values for XFS directory operation stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            lookups = stat[1]
            creates = stat[2]
            removes = stat[3]
            getdents = stat[4]
            perfdata += lib.base.get_perfdata(
                'directory_operation_create_total',
                creates,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'directory_operation_getdents_total',
                getdents,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'directory_operation_lookup_total',
                lookups,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'directory_operation_remove_total',
                removes,
                uom='c',
                _min=0,
            )

        # InodeOperationStats
        if stat[0] == 'ig':
            if len(stat) != 8:
                lib.base.oao(
                    'Incorrect number of values for XFS inode operation stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            attempts = stat[1]
            found = stat[2]
            recycle = stat[3]
            missed = stat[4]
            duplicate = stat[5]
            reclaims = stat[6]
            attributechange = stat[7]
            perfdata += lib.base.get_perfdata(
                'inode_operation_attempts_total',
                attempts,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_attribute_changes_total',
                attributechange,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_duplicates_total',
                duplicate,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_found_total',
                found,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_missed_total',
                missed,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_reclaims_total',
                reclaims,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'inode_operation_recycled_total',
                recycle,
                uom='c',
                _min=0,
            )

        # VnodeStats
        if stat[0] == 'vnodes':
            # The attribute "Free" appears to not be available on older XFS
            # stats versions.  Therefore, 7 or 8 elements may appear in
            # this slice.
            if len(stat) != 9:
                lib.base.oao(
                    'Incorrect number of values for XFS vnode stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            active = stat[1]
            allocate = stat[2]
            get = stat[3]
            hold = stat[4]
            release = stat[5]
            reclaim = stat[6]
            remove = stat[7]
            free = stat[8]
            perfdata += lib.base.get_perfdata(
                'vnode_active_total',
                active,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_allocate_total',
                allocate,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_free_total',
                free,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_get_total',
                get,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_hold_total',
                hold,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_reclaim_total',
                reclaim,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_release_total',
                release,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'vnode_remove_total',
                remove,
                uom='c',
                _min=0,
            )

        # ReadWriteStats
        if stat[0] == 'rw':
            if len(stat) != 3:
                lib.base.oao(
                    'Incorrect number of values for XFS read write stats.',
                    STATE_WARN,
                    perfdata,
                    always_ok=args.ALWAYS_OK,
                )
            writes = stat[1]
            reads = stat[2]
            perfdata += lib.base.get_perfdata(
                'read_calls_total',
                reads,
                uom='c',
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                'write_calls_total',
                writes,
                uom='c',
                _min=0,
            )

    # build the message
    msg = 'Everything is ok. Extent allocation, btree, block mapping, btree, directory operation, inode operation, vnode and read write stats collected.'

    # over and out
    lib.base.oao(msg, state, perfdata)


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
