Source code for mmstats.models
import ctypes
import os
import sys
import time
from . import fields, libgettid, _mmap
from .defaults import DEFAULT_PATH, DEFAULT_FILENAME
[docs]class FieldState(object):
"""Holds field state for each Field instance"""
def __init__(self, field):
self.field = field
[docs]class BaseMmStats(object):
"""Stats models should inherit from this"""
def __init__(self, path=DEFAULT_PATH, filename=DEFAULT_FILENAME,
label_prefix=None):
"""\
Optionally given a filename or label_prefix, create an MmStats instance
"""
self._removed = False
# Setup label prefix
self._label_prefix = '' if label_prefix is None else label_prefix
self._offset = 1
# Store state for this instance's fields
self._fields = {}
total_size = self._offset
#FIXME This is the *wrong* way to initialize stat fields
for cls in self.__class__.__mro__:
for attrname, attrval in cls.__dict__.items():
if (attrname not in self._fields
and isinstance(attrval, fields.Field)):
total_size += self._add_field(attrname, attrval)
self._fd, self._filename, self._size, self._mm_ptr = _mmap.init_mmap(
path=path, filename=filename, size=total_size)
mmap_t = ctypes.c_char * self._size
self._mmap = mmap_t.from_address(self._mm_ptr)
ver = ctypes.c_byte.from_address(self._mm_ptr)
ver.value = 1 # Version number
# Finally initialize thes stats
self._init_fields(total_size)
def _add_field(self, name, field):
"""Given a name and Field instance, add this field and retun size"""
# Stats need a place to store their per Mmstats instance state
state = self._fields[name] = FieldState(field)
# Call field._new to determine size
return field._new(state, self.label_prefix, name)
def _init_fields(self, total_size):
"""Once all fields have been added, initialize them in mmap"""
for state in self._fields.values():
# 2nd Call field._init to initialize new stat
self._offset = state.field._init(state, self._mm_ptr, self._offset)
@property
def filename(self):
return self._filename
@property
def label_prefix(self):
return self._label_prefix
@property
def size(self):
return self._size
[docs] def flush(self, async=False):
"""Flush mmapped file to disk"""
_mmap.msync(self._mm_ptr, self._size, async)
[docs] def remove(self):
"""Close and remove mmap file - No further stats updates will work"""
if self._removed:
# Make calling more than once a noop
return
_mmap.munmap(self._mm_ptr, self._size)
self._size = None
self._mm_ptr = None
self._mmap = None
os.close(self._fd)
try:
os.remove(self.filename)
except OSError:
# Ignore failed file removals
pass
# Remove fields to prevent segfaults
self._fields = {}
self._removed = True
[docs]class MmStats(BaseMmStats):
"""Mmstats default model base class
Just subclass, add your own fields, and instantiate:
>>> from mmstats.models import MmStats
>>> from mmstats.fields import CounterField
>>> class MyStats(MmStats):
... errors = CounterField()
...
>>> stats = MyStats()
>>> stats.errors.inc()
>>> stats.errors.value
1L
"""
pid = fields.StaticUIntField(label="sys.pid", value=os.getpid)
tid = fields.StaticInt64Field(label="sys.tid", value=libgettid.gettid)
uid = fields.StaticUInt64Field(label="sys.uid", value=os.getuid)
gid = fields.StaticUInt64Field(label="sys.gid", value=os.getgid)
python_version = fields.StaticTextField(label="org.python.version",
value=lambda: sys.version.replace("\n", ""))
created = fields.StaticDoubleField(label="sys.created", value=time.time)