Initial code motion
Rename things to just afsmon, and split out cmd from library.
This commit is contained in:
@@ -1 +1 @@
|
|||||||
include README.md
|
include README.rst
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
pyafsmon
|
afsmon
|
||||||
========
|
======
|
||||||
|
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,8 +11,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import argparse
|
|
||||||
import configparser
|
|
||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
import io
|
import io
|
||||||
@@ -25,9 +22,6 @@ from datetime import datetime
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
config = None
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Fileserver
|
# Fileserver
|
||||||
#
|
#
|
||||||
@@ -48,8 +42,13 @@ Volume = collections.namedtuple(
|
|||||||
class FileServerStats:
|
class FileServerStats:
|
||||||
'''AFS fileserver status
|
'''AFS fileserver status
|
||||||
|
|
||||||
|
Call ``get_stats()`` to populate the statistics for the server.
|
||||||
Note most attributes are only set if ``status`` is NORMAL
|
Note most attributes are only set if ``status`` is NORMAL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname (str): The hostname of server to query
|
||||||
|
i.e. argument to ``-server`` for cmd line tools
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
status (FileServerStatus): enum of possible status
|
status (FileServerStatus): enum of possible status
|
||||||
timestamp(:obj:`datetime.datetime`): time statistics retrieved
|
timestamp(:obj:`datetime.datetime`): time statistics retrieved
|
||||||
@@ -59,9 +58,10 @@ class FileServerStats:
|
|||||||
partition on the server
|
partition on the server
|
||||||
calls_waiting (:obj:`int`): number of calls waiting for a thread
|
calls_waiting (:obj:`int`): number of calls waiting for a thread
|
||||||
idle_threads (:obj:`int`): number of currently idle threads
|
idle_threads (:obj:`int`): number of currently idle threads
|
||||||
volumes (:obj:`list`): list of :obj:`Volume` tuples for each
|
volumes (:obj:`list`): list of :obj:`Volume` tuples for each
|
||||||
volume present on the server
|
volume present on the server
|
||||||
table (:obj:`PrettyTable`): a printable PrettyTable representation
|
table (:obj:`PrettyTable`): a printable PrettyTable representation
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def _get_volumes(self):
|
def _get_volumes(self):
|
||||||
@@ -70,6 +70,12 @@ class FileServerStats:
|
|||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
cmd, stderr=subprocess.STDOUT).decode('ascii')
|
cmd, stderr=subprocess.STDOUT).decode('ascii')
|
||||||
|
|
||||||
|
# Matching:
|
||||||
|
# mirror.yum-puppetlabs.readonly 536871036 RO 63026403 K On-line
|
||||||
|
vol_regex = re.compile(
|
||||||
|
'^(?P<vol>[^\s]+)\s+(?P<id>\d+)\s(?P<perms>R[OW])\s+(?P<used>\d+) K'
|
||||||
|
)
|
||||||
|
|
||||||
# Read the output into chunks where each chunk is the info for
|
# Read the output into chunks where each chunk is the info for
|
||||||
# one volume.
|
# one volume.
|
||||||
chunks = []
|
chunks = []
|
||||||
@@ -86,16 +92,15 @@ class FileServerStats:
|
|||||||
chunk += lines.readline()
|
chunk += lines.readline()
|
||||||
# convert it to a Volume()
|
# convert it to a Volume()
|
||||||
# todo: there's a bunch more we could extract...
|
# todo: there's a bunch more we could extract...
|
||||||
m = re.search(
|
m = vol_regex.search(chunk)
|
||||||
'^(?P<volume>[^\s]+)\s+(?P<id>\d+)\s(?P<perms>R[OW])\s+(?P<used>\d+) K',
|
|
||||||
chunk)
|
|
||||||
q = re.search('MaxQuota\s+(?P<quota>\d+) K', chunk)
|
q = re.search('MaxQuota\s+(?P<quota>\d+) K', chunk)
|
||||||
used = int(m['used'])
|
used = int(m['used'])
|
||||||
quota = int(q['quota'])
|
quota = int(q['quota'])
|
||||||
percent_used = round(float(used) / float(quota) * 100, 2)
|
percent_used = round(float(used) / float(quota) * 100, 2)
|
||||||
self.volumes.append(Volume(
|
self.volumes.append(
|
||||||
m['volume'], m['id'], m['perms'], used, quota, percent_used))
|
Volume(m['vol'], m['id'], m['perms'],
|
||||||
|
used, quota, percent_used))
|
||||||
|
|
||||||
|
|
||||||
def _get_calls_waiting(self):
|
def _get_calls_waiting(self):
|
||||||
cmd = ["rxdebug", self.hostname, "7000", "-rxstats", "-noconns"]
|
cmd = ["rxdebug", self.hostname, "7000", "-rxstats", "-noconns"]
|
||||||
@@ -161,13 +166,6 @@ class FileServerStats:
|
|||||||
'''Get the complete stats set for the fileserver'''
|
'''Get the complete stats set for the fileserver'''
|
||||||
self.timestamp = datetime.now()
|
self.timestamp = datetime.now()
|
||||||
|
|
||||||
self.restart = None
|
|
||||||
self.uptime = None
|
|
||||||
self.partitions = []
|
|
||||||
self.volumes = []
|
|
||||||
self.calls_waiting = None
|
|
||||||
self.idle_threads = None
|
|
||||||
|
|
||||||
self._get_fs_stats()
|
self._get_fs_stats()
|
||||||
if self.status == FileServerStatus.NORMAL:
|
if self.status == FileServerStatus.NORMAL:
|
||||||
self._get_partition_stats()
|
self._get_partition_stats()
|
||||||
@@ -193,6 +191,8 @@ class FileServerStats:
|
|||||||
self.table.add_row(["%s %%used" % n,
|
self.table.add_row(["%s %%used" % n,
|
||||||
"%s%%" % p.percent_used])
|
"%s%%" % p.percent_used])
|
||||||
for v in self.volumes:
|
for v in self.volumes:
|
||||||
|
# Only add the RW volumes to the table as for now we're
|
||||||
|
# mostly just worried about viewing the quota.
|
||||||
if v.perms == 'RW':
|
if v.perms == 'RW':
|
||||||
n = v.volume
|
n = v.volume
|
||||||
self.table.add_row(["%s used" % n, v.used])
|
self.table.add_row(["%s used" % n, v.used])
|
||||||
@@ -206,12 +206,21 @@ class FileServerStats:
|
|||||||
def __init__(self, hostname):
|
def __init__(self, hostname):
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
|
|
||||||
#
|
self.timestamp = None
|
||||||
# Volume
|
self.restart = None
|
||||||
#
|
self.uptime = None
|
||||||
|
self.partitions = []
|
||||||
|
self.volumes = []
|
||||||
|
self.calls_waiting = None
|
||||||
|
self.idle_threads = None
|
||||||
|
|
||||||
|
|
||||||
def get_fs_addresses(cell):
|
def get_fs_addresses(cell):
|
||||||
'''Get the fileservers associated with a cell'''
|
'''Get the fileservers associated with a cell
|
||||||
|
|
||||||
|
:arg str cell: The cell (e.g. ``openstack.org``)
|
||||||
|
:returns: list of fileservers for the cell
|
||||||
|
'''
|
||||||
fs = []
|
fs = []
|
||||||
cmd = ["vos", "listaddrs", "-noauth", "-cell", cell]
|
cmd = ["vos", "listaddrs", "-noauth", "-cell", cell]
|
||||||
logging.debug("Running: %s" % cmd)
|
logging.debug("Running: %s" % cmd)
|
||||||
@@ -227,62 +236,3 @@ def get_fs_addresses(cell):
|
|||||||
fs.append(line)
|
fs.append(line)
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
|
|
||||||
def get_volumes(cell):
|
|
||||||
'''Get the volumes in a cell'''
|
|
||||||
volumes = []
|
|
||||||
cmd = ["vos", "listvldb", "-quiet", "-noauth",
|
|
||||||
"-noresolve", "-nosort", "-cell", cell]
|
|
||||||
logging.debug("Running: %s" % cmd)
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
cmd, stderr=subprocess.STDOUT).decode('ascii')
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
logging.debug(" ... failed!")
|
|
||||||
return []
|
|
||||||
|
|
||||||
# details about the volumes are inset, so just look for non-blank lines
|
|
||||||
for line in output.split('\n'):
|
|
||||||
if line and not line.startswith(' '):
|
|
||||||
volumes.append(line.strip())
|
|
||||||
|
|
||||||
return volumes
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
global config
|
|
||||||
|
|
||||||
if args is None:
|
|
||||||
args = sys.argv[1:]
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='An AFS monitoring tool')
|
|
||||||
|
|
||||||
parser.add_argument("config", help="Path to config file")
|
|
||||||
parser.add_argument("-d", '--debug', action="store_true")
|
|
||||||
|
|
||||||
args = parser.parse_args(args)
|
|
||||||
|
|
||||||
if args.debug:
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
logging.debug("Debugging enabled")
|
|
||||||
|
|
||||||
config = configparser.RawConfigParser()
|
|
||||||
config.read(args.config)
|
|
||||||
|
|
||||||
cell = config.get('main', 'cell').strip()
|
|
||||||
|
|
||||||
# volumes = get_volumes(cell)
|
|
||||||
# logging.debug(volumes)
|
|
||||||
|
|
||||||
fileservers = get_fs_addresses(cell)
|
|
||||||
print(fileservers)
|
|
||||||
|
|
||||||
for fileserver in fileservers:
|
|
||||||
logging.debug("Finding stats for: %s" % fileserver)
|
|
||||||
|
|
||||||
fs = FileServerStats(fileserver)
|
|
||||||
fs.get_stats()
|
|
||||||
print(fs)
|
|
||||||
|
|
||||||
sys.exit(0)
|
|
53
afsmon/cmd/main.py
Normal file
53
afsmon/cmd/main.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import configparser
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import afsmon
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
|
||||||
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='An AFS monitoring tool')
|
||||||
|
|
||||||
|
parser.add_argument("config", help="Path to config file")
|
||||||
|
parser.add_argument("-d", '--debug', action="store_true")
|
||||||
|
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logging.debug("Debugging enabled")
|
||||||
|
|
||||||
|
config = configparser.RawConfigParser()
|
||||||
|
config.read(args.config)
|
||||||
|
|
||||||
|
cell = config.get('main', 'cell').strip()
|
||||||
|
|
||||||
|
fileservers = afsmon.get_fs_addresses(cell)
|
||||||
|
logging.debug("Found fileservers: %s" % ", ".join(fileservers))
|
||||||
|
|
||||||
|
for fileserver in fileservers:
|
||||||
|
logging.debug("Finding stats for: %s" % fileserver)
|
||||||
|
|
||||||
|
fs = afsmon.FileServerStats(fileserver)
|
||||||
|
fs.get_stats()
|
||||||
|
print(fs)
|
||||||
|
|
||||||
|
sys.exit(0)
|
0
afsmon/tests/__init__.py
Normal file
0
afsmon/tests/__init__.py
Normal file
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = pyafsmon
|
name = afsmon
|
||||||
summary = Monitoring for AFS
|
summary = Helpers for AFS monitoring in Python
|
||||||
description-file =
|
description-file =
|
||||||
README.rst
|
README.rst
|
||||||
author = Ian Wienand
|
author = Ian Wienand
|
||||||
@@ -19,9 +19,9 @@ classifier =
|
|||||||
|
|
||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
pyafsmon
|
afsmon
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
afsmon = pyafsmon.pyafsmon:main
|
afsmon = afsmon.cmd.main:main
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user