Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
language: python
python:
- 2.7
- 3.4
- 3.6
- 3.12
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -e .
# command to run tests, e.g. python setup.py test
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include LICENSE
include README.md
include pyformance/py.typed
15 changes: 7 additions & 8 deletions example_sysmetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import time
import json

import six
import psutil

from pyformance import global_registry
Expand All @@ -21,16 +20,16 @@ def __init__(self, registry=None):

def collect_disk_io(self, whitelist=[]):
stats = psutil.disk_io_counters(perdisk=True)
for entry, stat in six.iteritems(stats):
for entry, stat in stats.items():
if not whitelist or entry in whitelist:
for k, v in six.iteritems(stat._asdict()):
for k, v in stat._asdict().items():
self.registry.gauge("disk-%s.%s" % (entry, k)).set_value(v)

def collect_network_io(self, whitelist=[]):
stats = psutil.net_io_counters(pernic=True)
for entry, stat in six.iteritems(stats):
for entry, stat in stats.items():
if not whitelist or entry in whitelist:
for k, v in six.iteritems(stat._asdict()):
for k, v in stat._asdict().items():
self.registry.gauge(
"nic-%s.%s" % (entry.replace(" ", "_"), k)
).set_value(v)
Expand All @@ -39,17 +38,17 @@ def collect_cpu_times(self, whitelist=[]):
stats = psutil.cpu_times(percpu=True)
for entry, stat in enumerate(stats):
if not whitelist or entry in whitelist:
for k, v in six.iteritems(stat._asdict()):
for k, v in stat._asdict().items():
self.registry.gauge("cpu%d.%s" % (entry, k)).set_value(v)

def collect_swap_usage(self):
stats = psutil.swap_memory()
for k, v in six.iteritems(stats._asdict()):
for k, v in stats._asdict().items():
self.registry.gauge("swap.%s" % k).set_value(v)

def collect_virtmem_usage(self):
stats = psutil.virtual_memory()
for k, v in six.iteritems(stats._asdict()):
for k, v in stats._asdict().items():
self.registry.gauge("virtmem.%s" % k).set_value(v)

def collect_uptime(self):
Expand Down
52 changes: 39 additions & 13 deletions pyformance/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
__import__("pkg_resources").declare_namespace(__name__)

from .registry import MetricsRegistry, global_registry, set_global_registry
from .registry import timer, counter, meter, histogram, gauge
from .registry import (
dump_metrics,
clear,
count_calls,
meter_calls,
hist_calls,
time_calls,
)
from .meters.timer import call_too_long
"""
Copyright 2014 Omer Gertel
Copyright 2025 Inmanta

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.
"""

from typing import Protocol


class Clock(Protocol):

def time(self) -> float: ...


from .registry import MetricsRegistry as MetricsRegistry # noqa F401
from .registry import clear as clear # noqa F401
from .registry import count_calls as count_calls # noqa F401
from .registry import counter as counter # noqa F401
from .registry import dump_metrics as dump_metrics # noqa F401
from .registry import gauge as gauge # noqa F401
from .registry import global_registry as global_registry # noqa F401
from .registry import hist_calls as hist_calls # noqa F401
from .registry import histogram as histogram # noqa F401
from .registry import meter as meter # noqa F401
from .registry import meter_calls as meter_calls # noqa F401
from .registry import set_global_registry as set_global_registry # noqa F401
from .registry import time_calls as time_calls # noqa F401
from .registry import timer as timer # noqa F401
25 changes: 23 additions & 2 deletions pyformance/meters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
"""
Copyright 2014 Omer Gertel
Copyright 2025 Inmanta

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.
"""

from .counter import Counter
from .meter import Meter
from .gauge import CallbackGauge, Gauge, SimpleGauge
from .histogram import Histogram
from .meter import Meter
from .timer import Timer
from .gauge import Gauge, CallbackGauge, SimpleGauge

__all__ = ["Counter", "CallbackGauge", "Gauge", "SimpleGauge", "Histogram", "Meter", "Timer"]

type any_meter = Histogram | Meter | Gauge[int | float] | Timer | Counter
28 changes: 22 additions & 6 deletions pyformance/meters/counter.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
"""
Copyright 2014 Omer Gertel
Copyright 2025 Inmanta

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.
"""

from threading import Lock


class Counter(object):

"""
An incrementing and decrementing metric
"""

def __init__(self):
def __init__(self) -> None:
super(Counter, self).__init__()
self.lock = Lock()
self.counter = 0

def inc(self, val=1):
def inc(self, val: int = 1) -> None:
"increment counter by val (default is 1)"
with self.lock:
self.counter = self.counter + val

def dec(self, val=1):
def dec(self, val: int = 1) -> None:
"decrement counter by val (default is 1)"
self.inc(-val)

def get_count(self):
def get_count(self) -> int:
"return current value of counter"
return self.counter

def clear(self):
def clear(self) -> None:
"reset counter to 0"
with self.lock:
self.counter = 0
50 changes: 35 additions & 15 deletions pyformance/meters/gauge.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,77 @@
class Gauge(object):
"""
Copyright 2014 Omer Gertel
Copyright 2025 Inmanta

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.
"""

from typing import Callable, Union


class Gauge[T: Union[int, float]]:
"""
A base class for reading of a particular.

For example, to instrument a queue depth:

class QueueLengthGaguge(Gauge):
def __init__(self, queue):
super(QueueGaguge, self).__init__()
self.queue = queue

def get_value(self):
return len(self.queue)

"""

def get_value(self):
def get_value(self) -> T:
"A subclass of Gauge should implement this method"
raise NotImplementedError()


class CallbackGauge(Gauge):

class CallbackGauge[T: Union[int, float]](Gauge[T]):
"""
A Gauge reading for a given callback
"""

def __init__(self, callback):
def __init__(self, callback: Callable[[], T]) -> None:
"constructor expects a callable"
super(CallbackGauge, self).__init__()
self.callback = callback

def get_value(self):
def get_value(self) -> T:
"returns the result of callback which is executed each time"
return self.callback()


class SimpleGauge(Gauge):

class SimpleGauge[T: Union[int, float]](Gauge[T]):
"""
A gauge which holds values with simple getter- and setter-interface
"""

def __init__(self, value=float("nan")):
def __init__(self, value: T) -> None:
"constructor accepts initial value"
super(SimpleGauge, self).__init__()
self._value = value

def get_value(self):
def get_value(self) -> T:
"getter returns current value"
return self._value

def set_value(self, value):
def set_value(self, value: T) -> None:
"setter changes current value"
# XXX: add locking?
self._value = value


type AnyGauge = Gauge[float | int]
Loading