-
Notifications
You must be signed in to change notification settings - Fork 227
system metrics (CPU/memory) #361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d499d8b
first stab at implementing metrics [WIP]
beniwohli 20d7051
don't log so loudly
beniwohli fb9c62b
Merge branch 'master' into metrics-support
beniwohli 7c5de3f
reschedule timer thread after it runs
beniwohli 5817631
added some inline documentation
beniwohli e0fc533
added cpu/memory metrics collection based on /proc for linux
beniwohli 17f3a7d
(somewhat) fix CPU usage calculation
beniwohli 3048596
applied fixes as per @axw's review, and added a test
beniwohli 6ed4a4b
moved metric sets into sub package, and added some docs
beniwohli 7ab887d
fixed psutil calculations and added a test and test matrix
beniwohli f700c15
fixed tests
beniwohli aca3439
added test to compare output from psutil with our own proc parsing
beniwohli e183288
Merge branch 'master' into metrics-support
beniwohli e27e15a
make sure we don't start metrics collection in tests when not needed
beniwohli 3b291f4
skip cpu_linux metrics tests on non-Linux hosts
beniwohli b5c1940
install psutil on appveyor
beniwohli 0d616bb
Merge branch 'master' into metrics-support
beniwohli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[[metrics]] | ||
== Metrics | ||
|
||
With Elastic APM, you can capture system and process metrics. | ||
These metrics will be sent regularly to the APM Server and from there to Elasticsearch | ||
|
||
[[metric-sets]] | ||
=== Metric Sets | ||
|
||
[[cpu-memory-metricset]] | ||
==== CPU/Memory Metric Set | ||
|
||
`elasticapm.metrics.sets.cpu.CPUMetricSet` | ||
|
||
This metric set collects various system metrics and metrics of the current process. | ||
|
||
|============ | ||
| Metric | Type | Format | Description | ||
| `system.cpu.total.norm.pct`| scaled_float | percent| The percentage of CPU time in states other than Idle and IOWait, normalised by the number of cores. | ||
| `system.memory.total`| long | bytes | The total memory of the system in bytes | ||
| `system.memory.actual.free`| long | bytes | Actual free memory in bytes | ||
| `system.process.cpu.total.norm.pct`| scaled_float | percent | The percentage of CPU time spent by the process since the last event. This value is normalized by the number of CPU cores and it ranges from 0 to 100%. | ||
| `system.process.memory.size`| long | bytes | The total virtual memory the process has. | ||
| `system.process.memory.rss.bytes`| long | bytes | The Resident Set Size. The amount of memory the process occupied in main memory (RAM). | ||
|============ | ||
|
||
NOTE: if you do *not* use Linux, you need to install https://pypi.org/project/psutil/[`psutil`] for this metric set. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import logging | ||
import threading | ||
import time | ||
|
||
from elasticapm.utils import compat | ||
from elasticapm.utils.module_import import import_string | ||
|
||
logger = logging.getLogger("elasticapm.metrics") | ||
|
||
|
||
class MetricsRegistry(object): | ||
def __init__(self, collect_interval, queue_func, tags=None): | ||
""" | ||
Creates a new metric registry | ||
|
||
:param collect_interval: the interval to collect metrics from registered metric sets | ||
:param queue_func: the function to call with the collected metrics | ||
:param tags: | ||
""" | ||
self._collect_interval = collect_interval | ||
self._queue_func = queue_func | ||
self._metricsets = {} | ||
self._tags = tags or {} | ||
self._collect_timer = None | ||
if self._collect_interval: | ||
self._start_collect_timer() | ||
|
||
def register(self, class_path): | ||
""" | ||
Register a new metric set | ||
:param class_path: a string with the import path of the metricset class | ||
""" | ||
if class_path in self._metricsets: | ||
return | ||
else: | ||
try: | ||
class_obj = import_string(class_path) | ||
self._metricsets[class_path] = class_obj() | ||
except ImportError as e: | ||
logger.warning("Could not register %s metricset: %s", class_path, compat.text_type(e)) | ||
|
||
def collect(self, start_timer=True): | ||
""" | ||
Collect metrics from all registered metric sets | ||
:param start_timer: if True, restarts the collect timer after collection | ||
:return: | ||
""" | ||
if start_timer: | ||
self._start_collect_timer() | ||
|
||
logger.debug("Collecting metrics") | ||
|
||
for name, metricset in compat.iteritems(self._metricsets): | ||
data = metricset.collect() | ||
if data: | ||
self._queue_func("metricset", data) | ||
|
||
def _start_collect_timer(self, timeout=None): | ||
timeout = timeout or self._collect_interval | ||
self._collect_timer = threading.Timer(timeout, self.collect, kwargs={"start_timer": True}) | ||
self._collect_timer.name = "elasticapm metrics collect timer" | ||
self._collect_timer.daemon = True | ||
logger.debug("Starting metrics collect timer") | ||
self._collect_timer.start() | ||
|
||
def _stop_collect_timer(self): | ||
if self._collect_timer: | ||
logger.debug("Cancelling collect timer") | ||
self._collect_timer.cancel() | ||
|
||
|
||
class MetricsSet(object): | ||
def __init__(self): | ||
self._lock = threading.Lock() | ||
self._counters = {} | ||
self._gauges = {} | ||
|
||
def counter(self, name): | ||
""" | ||
Returns an existing or creates and returns a new counter | ||
:param name: name of the counter | ||
:return: the counter object | ||
""" | ||
with self._lock: | ||
if name not in self._counters: | ||
self._counters[name] = Counter(name) | ||
return self._counters[name] | ||
|
||
def gauge(self, name): | ||
""" | ||
Returns an existing or creates and returns a new gauge | ||
:param name: name of the gauge | ||
:return: the gauge object | ||
""" | ||
with self._lock: | ||
if name not in self._gauges: | ||
self._gauges[name] = Gauge(name) | ||
return self._gauges[name] | ||
|
||
def collect(self): | ||
""" | ||
Collects all metrics attached to this metricset, and returns it as a list, together with a timestamp | ||
in microsecond precision. | ||
|
||
The format of the return value should be | ||
|
||
{ | ||
"samples": {"metric.name": {"value": some_float}, ...}, | ||
"timestamp": unix epoch in microsecond precision | ||
} | ||
""" | ||
samples = {} | ||
if self._counters: | ||
samples.update({label: {"value": c.val} for label, c in compat.iteritems(self._counters)}) | ||
if self._gauges: | ||
samples.update({label: {"value": g.val} for label, g in compat.iteritems(self._gauges)}) | ||
if samples: | ||
return {"samples": samples, "timestamp": int(time.time() * 1000000)} | ||
|
||
|
||
class Counter(object): | ||
__slots__ = ("label", "_lock", "_initial_value", "_val") | ||
|
||
def __init__(self, label, initial_value=0): | ||
""" | ||
Creates a new counter | ||
:param label: label of the counter | ||
:param initial_value: initial value of the counter, defaults to 0 | ||
""" | ||
self.label = label | ||
self._lock = threading.Lock() | ||
self._val = self._initial_value = initial_value | ||
|
||
def inc(self, delta=1): | ||
""" | ||
Increments the counter. If no delta is provided, it is incremented by one | ||
:param delta: the amount to increment the counter by | ||
""" | ||
with self._lock: | ||
self._val += delta | ||
|
||
def dec(self, delta=1): | ||
""" | ||
Decrements the counter. If no delta is provided, it is decremented by one | ||
:param delta: the amount to decrement the counter by | ||
""" | ||
with self._lock: | ||
self._val -= delta | ||
|
||
def reset(self): | ||
""" | ||
Reset the counter to the initial value | ||
""" | ||
with self._lock: | ||
self._val = self._initial_value | ||
|
||
@property | ||
def val(self): | ||
"""Returns the current value of the counter""" | ||
return self._val | ||
|
||
|
||
class Gauge(object): | ||
__slots__ = ("label", "_val") | ||
|
||
def __init__(self, label): | ||
""" | ||
Creates a new gauge | ||
:param label: label of the gauge | ||
""" | ||
self.label = label | ||
self._val = None | ||
|
||
@property | ||
def val(self): | ||
return self._val | ||
|
||
@val.setter | ||
def val(self, value): | ||
self._val = value |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import os | ||
import platform | ||
|
||
if platform.system() == "Linux" and "ELASTIC_APM_FORCE_PSUTIL_METRICS" not in os.environ: | ||
graphaelli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from elasticapm.metrics.sets.cpu_linux import CPUMetricSet # noqa: F401 | ||
else: | ||
from elasticapm.metrics.sets.cpu_psutil import CPUMetricSet # noqa: F401 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.