Skip to content
Browse files

Merge branch 'master' into pypy-compat

  • Loading branch information...
2 parents fcf1316 + 2b913cf commit 7c27381c32dadf79a47a9ef20795a66e23b0e910 @schmichael committed Nov 26, 2011
Showing with 64 additions and 47 deletions.
  1. +11 −0 CHANGES.txt
  2. +13 −9 README.rst
  3. +7 −4 examples/basic_flask.py
  4. +19 −30 mmstats.py
  5. +2 −1 run_flask_example
  6. +1 −1 setup.py
  7. +3 −2 slurpstats.py
  8. +8 −0 tests/test_types.py
View
11 CHANGES.txt
@@ -1,3 +1,14 @@
+Version 0.3.8 "Hapiness" released 2011-11-20
+
+* Allow filename templating with %PID% and %TID% placeholders
+* Allow setting filename template via MMSTATS_FILES environment variable
+* Improved docs slightly
+* Fixed Ctrl-Cing run_flask_example script
+* Fixed 64 bit integer fields on 32 bit platforms
+* Fixed StaticInt64Field (was actually a uint64 field before)
+* Fixed slurpstats debug output (always showed first 40 bytes of file)
+* Strip newlines from org.python.version
+
Version 0.3.7 "Depressive Realism is for Winners" released 2011-11-17
* Add pollstats utility (similar to dstat/vmstat)
View
22 README.rst
@@ -2,11 +2,11 @@
About
=====
-Mmstats is a way to expose and read (slurpstats.py) diagnostic/statistical
-values for applications.
+Mmstats is a way to expose (mmstats.py) and read (slurpstats, pollstats, mmash)
+diagnostic/statistical values for applications.
-You could think of mmstats as /proc for your application and slurpstats.py as one
-of the procps tools.
+Think of mmstats as /proc for your application and the readers as procps
+utilities.
-----
Goals
@@ -17,7 +17,7 @@ Goals
* Predictable performance impact for writers via:
* No locks (1 writer per thread)
- * No syscalls
+ * No syscalls (after instantiation)
* All in userspace
* Reading has no impact on writers
@@ -65,8 +65,10 @@ Using
if response.status_code == 200:
webstats.status2xx += 1
-6. Run ``python slurpstats.py`` to read it
-7. Run ``python mmash.py`` to create a web interface for stats
+6. Run ``slurpstats`` to read it
+7. Run ``mmash`` to create a web interface for stats
+8. Run ``pollstats -p web.stats.status 2XX,3XX,4XX,5XX /tmp/mmstats-*`` for a
+ vmstat/dstat like view.
-----------
Development
@@ -93,11 +95,13 @@ Now to view the stats run the following in a new terminal:
::
$ # To get a raw view of the data:
- $ slurpstats
+ $ slurpstats mmstats-*
$ # Or start up the web interface:
$ mmash
+ $ # Run pollstats while ab is running:
+ $ pollstats -p flask.example. ok,bad,working mmstats-*
-To cleanup stray mmstats files you probably need to do: ``rm /tmp/mmstats-*``
+To cleanup stray mmstats files: ``rm mmstats-flask-*``
The web interface will automatically reload when you change source files.
View
11 examples/basic_flask.py
@@ -1,3 +1,5 @@
+import atexit
+
import flask
import mmstats
@@ -8,11 +10,12 @@
class Stats(mmstats.MmStats):
- ok = mmstats.CounterField(label="mmstats.example.ok")
- bad = mmstats.CounterField(label="mmstats.example.bad")
- working = mmstats.BoolField(label="mmstats.example.working")
+ ok = mmstats.CounterField(label="flask.example.ok")
+ bad = mmstats.CounterField(label="flask.example.bad")
+ working = mmstats.BoolField(label="flask.example.working")
-stats = Stats()
+stats = Stats(filename='mmstats-flask-example-%PID%')
+atexit.register(stats.remove)
def set_working(sender):
View
49 mmstats.py
@@ -13,22 +13,22 @@
SIZE_TYPE = ctypes.c_ushort
WRITE_BUFFER_UNUSED = 255
DEFAULT_PATH = os.environ.get('MMSTATS_PATH', tempfile.gettempdir())
+DEFAULT_FILENAME = os.environ.get('MMSTATS_FILES', 'mmstats-%PID%-%TID%')
class DuplicateFieldName(Exception):
"""Cannot add 2 fields with the same name to MmStat instances"""
-def _init_mmap(path=None, filename=None, size=PAGESIZE):
- """Given path, filename => filename, size, mmap"""
- if path is None:
- path = DEFAULT_PATH
+def _init_mmap(path=DEFAULT_PATH, filename=DEFAULT_FILENAME, size=PAGESIZE):
+ """Given path, filename => filename, size, mmap
- if filename is None:
- filename = 'mmstats-%d' % os.getpid()
- tid = libgettid.gettid()
- if tid:
- filename += '-%d' % tid
+ In `filename` "%PID%" and "%TID%" will be replaced with pid and thread id
+ """
+ # Replace %PID% with actual pid
+ filename = filename.replace('%PID%', str(os.getpid()))
+ # Replace %TID% with thread id or 0 if thread id is None
+ filename = filename.replace('%TID%', str(libgettid.gettid() or 0))
full_path = os.path.join(path, filename)
@@ -226,7 +226,7 @@ def inc(self, n=1):
class CounterField(DoubleBufferedField):
"""Counter field supporting an inc() method and value attribute"""
buffer_type = ctypes.c_uint64
- type_signature = 'L'
+ type_signature = 'Q'
def _init(self, state, mm_ptr, offset):
offset = super(CounterField, self)._init(state, mm_ptr, offset)
@@ -279,7 +279,7 @@ class BufferedDescriptorField(DoubleBufferedField, BufferedDescriptorMixin):
class UInt64Field(BufferedDescriptorField):
"""Unbuffered read-only 64bit Unsigned Integer field"""
buffer_type = ctypes.c_uint64
- type_signature = 'L'
+ type_signature = 'Q'
class UIntField(BufferedDescriptorField):
@@ -345,15 +345,15 @@ class StaticUIntField(ReadOnlyField):
class StaticInt64Field(ReadOnlyField):
- """Unbuffered read-only 64bit Unsigned Integer field"""
- buffer_type = ctypes.c_uint64
- type_signature = 'l'
+ """Unbuffered read-only 64bit Signed Integer field"""
+ buffer_type = ctypes.c_int64
+ type_signature = 'q'
class StaticUInt64Field(ReadOnlyField):
"""Unbuffered read-only 64bit Unsigned Integer field"""
buffer_type = ctypes.c_uint64
- type_signature = 'L'
+ type_signature = 'Q'
class StaticTextField(ReadOnlyField):
@@ -383,7 +383,7 @@ def __init__(self, field):
class BaseMmStats(object):
"""Stats models should inherit from this"""
- def __init__(self, filename=None, label_prefix=None):
+ def __init__(self, filename=DEFAULT_FILENAME, label_prefix=None):
"""\
Optionally given a filename or label_prefix, create an MmStats instance
"""
@@ -459,24 +459,13 @@ class MmStats(BaseMmStats):
tid = StaticInt64Field(label="sys.tid", value=libgettid.gettid)
uid = StaticUInt64Field(label="sys.uid", value=os.getuid)
gid = StaticUInt64Field(label="sys.gid", value=os.getgid)
- python_version = StaticTextField(
- label="org.python.version", value=sys.version)
+ python_version = StaticTextField(label="org.python.version",
+ value=lambda: sys.version.replace("\n", ""))
+ #TODO Add the following fields? sys.path might be a little overboard
"""
- python_version_info = StaticTextField(
- label="org.python.version_info",
- value=sys.version_info
- )
-
argv = StaticListField(label="sys.argv", item_type=str, value=sys.argv)
- env = StaticMappingField(label="sys.env", item_type=str, value=os.environ)
created = StaticUInt64Field(
label="sys.created", value=lambda: int(time.time()))
- python_version = StaticTextField(
- label="org.python.version", value=sys.version)
- python_version_info = StaticTextField(
- label="org.python.version_info",
- value=sys.version_info
- )
python_path = StaticTextField(
label="org.python.path",
item_type=str,
View
3 run_flask_example
@@ -1,4 +1,5 @@
#!/bin/sh
echo "Running a basic flask app via gunicorn with 4 processes to demonstrate"
echo "cross-process metric aggregation"
-gunicorn -b 0.0.0.0:5001 -w 4 examples.basic_flask
+MMSTATS_PATH=$(pwd)
+exec gunicorn -b 0.0.0.0:5001 -w 4 examples.basic_flask
View
2 setup.py
@@ -12,7 +12,7 @@
setup(
name='mmstats',
url='https://github.com/schmichael/mmstats',
- version='0.3.7',
+ version='0.3.8',
license='BSD',
author='Michael Schurter',
author_email='m@schmichael.com',
View
5 slurpstats.py
@@ -29,8 +29,9 @@ def err(*args):
def slurp_v1(m):
"""Reads a single mmstat v1 record"""
- dbg(repr(m[0:20]))
- dbg(repr(m[20:40]))
+ offset = m.tell()
+ dbg(repr(m[offset:offset+20]))
+ dbg(repr(m[offset+20:offset+40]))
label_sz = struct.unpack('H', m.read(2))[0]
label = m.read(label_sz)
dbg(label_sz, label)
View
8 tests/test_types.py
@@ -1,3 +1,6 @@
+import ctypes
+import struct
+
from . import base
import mmstats
@@ -119,6 +122,11 @@ class MixedStats(mmstats.MmStats):
self.assertEqual(m1.e, 1, m1.e)
self.assertEqual(m2.e, 90, m2.e)
+ def test_counter_sz(self):
+ self.assertEquals(ctypes.sizeof(mmstats.CounterField.buffer_type), 8)
+ self.assertEquals(
+ struct.calcsize(mmstats.CounterField.type_signature), 8)
+
def test_counter(self):
class SimpleCounter(mmstats.MmStats):
counter = mmstats.CounterField()

0 comments on commit 7c27381

Please sign in to comment.
Something went wrong with that request. Please try again.