# # Copyright (c) 2018 Red Hat, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # """ Field - base class for a field of a collection of statistics $Id: //eng/vdo-releases/magnesium/src/python/vdo/statistics/Field.py#1 $ """ from ctypes import * from LabeledValue import LabeledValue import re class Field(object): """ Field is the base class for a field of a statistics structure. """ decamelRE1 = re.compile(r'([A-Z][a-z])') decamelRE2 = re.compile(r'([a-z])([A-Z])') fieldNames = re.compile(r'\$([a-zA-Z0-9_]+)') @classmethod def _decamel(cls, string): """ Convert a camel-cased string to a set of space separated, lower case words. Preserves uppercase acronyms, so 'launchVDOErrorCount' becomes 'launch VDO error count'. :param string: The string to convert :return: The converted string """ lowered = cls.decamelRE1.sub(lambda match: " " + match.group().lower(), string) return cls.decamelRE2.sub(lambda match: " ".join(match.groups()), lowered) def _generateLambda(self, string): """ Convert a string describing how to derive a field's value into a lambda. :param string The string to convert :return: An equivalent lambda """ derivation = self.fieldNames.sub(r'parent.getSampleValue(stats, "\1")', string) return lambda stats, parent: eval(derivation) def __init__(self, name, cType, **kwargs): """ Create a new field. :param name: The name of the field :param cType: The class representing the C representation for this field when sampled via an ioctl :param **kwargs: Keyword arguments which may be: available: Specifies python code to apply to other fields of the parent structure to decide whether this value is available. Defaults to True. derived: Specifies python code to apply to other fields of the parent structure to derive the value of this field. Defaults to None. display: If not True, this field will not be included in labeled output. Defaults to True. label: The label for this field. If unspecified, the label will be derived from the field name. length: if > 1, indicates this field is an array of the specified cType, otherwise is is a scalar. Defaults to 1. """ self.name = name self.length = kwargs.pop('length', 1) self.cType = cType * self.length if (self.length > 1) else cType self.display = kwargs.pop('display', True) self.label = (kwargs.pop('label', self._decamel(self.name)) if self.display else None) self.available = self._generateLambda(kwargs.pop('available', "True")) # While all stats have C types, not all stats are present in the # struct returned via the ioctl. derived = kwargs.pop('derived', None) self.inStruct = (derived is None) defaultValue = "getattr(stats, '{name}')".format(name = self.name) self.getValue = self._generateLambda(derived if derived else defaultValue) if kwargs: raise Exception("unknown arguments to Field: {0}".format(kwargs.keys())) def extractSample(self, stats, parent): """ Extract the value for this field from a sample. :param stats: The raw stats returned from an ioctl :param parent: The parent of this field :return: The value of this field in the current sample """ if not self.available(stats, parent): return NotAvailable() return self.getValue(stats, parent) def labeled(self, sample, prefix): """ Label a sampled value for this field. :param sample: The sampled field value :param prefix: The prefix for the label :return: A LabeledValue for a value of this field """ return LabeledValue.make(prefix + self.label, sample) # base integer class IntegerField(Field): """ Base class for fields which are integer types. """ def extractSample(self, stats, parent): """ :inherit: """ return int(super(IntegerField, self).extractSample(stats, parent)) # basic integer types class BoolField(IntegerField): def __init__(self, name, **kwargs): super(BoolField, self).__init__(name, c_byte, **kwargs) def extractSample(self, stats, parent): return (super(BoolField, self).extractSample(stats, parent) != 0) class Uint8Field(IntegerField): def __init__(self, name, **kwargs): super(Uint8Field, self).__init__(name, c_byte, **kwargs) class Uint32Field(IntegerField): def __init__(self, name, **kwargs): super(Uint32Field, self).__init__(name, c_uint, **kwargs) class Uint64Field(IntegerField): def __init__(self, name, **kwargs): super(Uint64Field, self).__init__(name, c_ulonglong, **kwargs) # base float class FloatingPointField(Field): """ Base class for fields which are floating point types. """ def extractSample(self, stats, parent): """ :inherit: """ return float(super(FloatingPointField, self).extractSample(stats, parent)) # basic float type class FloatField(FloatingPointField): def __init__(self, name, **kwargs): super(FloatField, self).__init__(name, c_float, **kwargs) # the basic string type class StringField(Field): def __init__(self, name, **kwargs): super(StringField, self).__init__(name, c_char, **kwargs) # not available class NotAvailable(int): """ A value for numeric statistics which are currently not available; prints as 'N/A'. """ def __str__(self): return "N/A" def __repr__(self): return "NotAvailable" def __add__(self, other): return self def __radd__(self, other): return self def __sub__(self, other): return self def __rsub__(self, other): return self def __mul__(self, other): return self def __rmul__(self, other): return self def __div__(self, other): return self def __rdiv__(self, other): return self def __int__(self): return self def __format__(self, spec): return ("{0:" + spec + "}").format(str(self))