# Linux OS parameter collector (SERVER-1794)
#
# Kernel / network / vm / fs tunables are read through sysctl.  Default keys
# are managed here in code; extra keys can be requested by the user through
# the custom option.

import platform
import threading
import whatap.util.process_util as process_util
from whatap.osinfo.os_parameter_common import OsParameter, BackgroundCollector

OS_NAME = platform.system() or 'Linux'

# default sysctl keys (see linux.sh reference)
_DEFAULT_KEYS = [
    'net.core.somaxconn', 'net.core.netdev_max_backlog', 'net.core.rmem_default',
    'net.core.rmem_max', 'net.core.wmem_default', 'net.core.wmem_max',
    'net.core.optmem_max', 'net.core.default_qdisc',
    'net.ipv4.ip_forward', 'net.ipv4.ip_local_port_range', 'net.ipv4.tcp_syncookies',
    'net.ipv4.tcp_max_syn_backlog', 'net.ipv4.tcp_syn_retries',
    'net.ipv4.tcp_synack_retries', 'net.ipv4.tcp_fin_timeout',
    'net.ipv4.tcp_tw_reuse', 'net.ipv4.tcp_max_tw_buckets',
    'net.ipv4.tcp_keepalive_time', 'net.ipv4.tcp_keepalive_intvl',
    'net.ipv4.tcp_keepalive_probes', 'net.ipv4.tcp_rmem', 'net.ipv4.tcp_wmem',
    'net.ipv4.tcp_congestion_control', 'net.ipv4.tcp_mtu_probing',
    'net.ipv4.udp_rmem_min', 'net.ipv4.udp_wmem_min',
    'net.netfilter.nf_conntrack_max', 'net.netfilter.nf_conntrack_buckets',
    'net.netfilter.nf_conntrack_tcp_timeout_established',
    'net.netfilter.nf_conntrack_tcp_timeout_time_wait',
    'net.netfilter.nf_conntrack_tcp_timeout_close_wait',
    'net.netfilter.nf_conntrack_udp_timeout',
    'net.netfilter.nf_conntrack_udp_timeout_stream',
    'net.ipv6.conf.all.disable_ipv6', 'net.ipv6.conf.default.disable_ipv6',
    'net.ipv6.conf.lo.disable_ipv6',
    'kernel.pid_max', 'kernel.threads-max', 'kernel.panic', 'kernel.panic_on_oops',
    'kernel.sysrq', 'kernel.core_pattern', 'kernel.core_uses_pid',
    'kernel.numa_balancing', 'kernel.sem', 'kernel.shmmax', 'kernel.shmall',
    'kernel.shmmni', 'kernel.msgmax', 'kernel.msgmnb', 'kernel.msgmni',
    'kernel.randomize_va_space', 'kernel.kptr_restrict', 'kernel.dmesg_restrict',
    'kernel.perf_event_paranoid', 'kernel.yama.ptrace_scope',
    'vm.swappiness', 'vm.overcommit_memory', 'vm.overcommit_ratio',
    'vm.max_map_count', 'vm.min_free_kbytes', 'vm.dirty_ratio',
    'vm.dirty_background_ratio', 'vm.dirty_expire_centisecs',
    'vm.dirty_writeback_centisecs', 'vm.vfs_cache_pressure', 'vm.zone_reclaim_mode',
    'vm.nr_hugepages', 'vm.hugetlb_shm_group',
    'fs.file-max', 'fs.nr_open', 'fs.aio-max-nr', 'fs.inotify.max_user_watches',
    'fs.inotify.max_user_instances', 'fs.inotify.max_queued_events',
    'fs.protected_hardlinks', 'fs.protected_symlinks', 'fs.suid_dumpable',
]

_custom_params = []
_custom_lock = threading.Lock()


def SetCustomParams(params):
    global _custom_params
    with _custom_lock:
        _custom_params = list(params or [])


def _get_custom_params():
    with _custom_lock:
        return list(_custom_params)


def _category_of(key):
    if key.startswith('net.'):
        return 'network'
    if key.startswith('kernel.'):
        return 'kernel'
    if key.startswith('vm.'):
        return 'memory'
    if key.startswith('fs.'):
        return 'fs'
    return 'unknown'


def _sysctl(key):
    try:
        out = process_util.executeCommandShellWithTimeout(
            'sysctl -n %s 2>/dev/null' % key, timeout=5)
    except Exception:
        return None
    if out is None or out == '':
        return None
    return out.replace('\n', ' ').replace(',', ' ').strip()


def _collect():
    result = []
    seen = set()

    for key in _DEFAULT_KEYS:
        value = _sysctl(key)
        if value is None or value == '':
            continue
        category = _category_of(key)
        result.append(OsParameter(key, category, OS_NAME, value, custom=False))
        seen.add((key, category))

    for param, category_hint in _get_custom_params():
        value = _sysctl(param)
        if value is None or value == '':
            continue
        category = category_hint or _category_of(param)
        if (param, category) in seen:
            continue
        result.append(OsParameter(param, category, OS_NAME, value, custom=True))
        seen.add((param, category))

    return result


_collector = BackgroundCollector('os_parameter_linux', _collect, interval=300)


def GetOsParameterList(custom_params=None):
    if custom_params is not None:
        SetCustomParams(custom_params)
    return _collector.get()
