mirror of
https://github.com/netdata/netdata.git
synced 2025-05-16 06:11:38 +00:00

<!-- Describe the change in summary section, including rationale and degin decisions. Include "Fixes #nnn" if you are fixing an existing issue. In "Component Name" section write which component is changed in this PR. This will help us review your PR quicker. If you have more information you want to add, write them in "Additional Information" section. This is usually used to help others understand your motivation behind this change. A step-by-step reproduction of the problem is helpful if there is no related issue. --> ##### Summary Add memory free Add per process memory usage ##### Component Name nvidia_smi ##### Additional Information
407 lines
13 KiB
Python
407 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Description:
|
|
# Author: Ilya Mashchenko (ilyam8)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from bases.collection import safe_print
|
|
|
|
CHART_PARAMS = ['type', 'id', 'name', 'title', 'units', 'family', 'context', 'chart_type', 'hidden']
|
|
DIMENSION_PARAMS = ['id', 'name', 'algorithm', 'multiplier', 'divisor', 'hidden']
|
|
VARIABLE_PARAMS = ['id', 'value']
|
|
|
|
CHART_TYPES = ['line', 'area', 'stacked']
|
|
DIMENSION_ALGORITHMS = ['absolute', 'incremental', 'percentage-of-absolute-row', 'percentage-of-incremental-row']
|
|
|
|
CHART_BEGIN = 'BEGIN {type}.{id} {since_last}\n'
|
|
CHART_CREATE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \
|
|
"{chart_type} {priority} {update_every} '{hidden}' 'python.d.plugin' '{module_name}'\n"
|
|
CHART_OBSOLETE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \
|
|
"{chart_type} {priority} {update_every} '{hidden} obsolete'\n"
|
|
|
|
|
|
DIMENSION_CREATE = "DIMENSION '{id}' '{name}' {algorithm} {multiplier} {divisor} '{hidden} {obsolete}'\n"
|
|
DIMENSION_SET = "SET '{id}' = {value}\n"
|
|
|
|
CHART_VARIABLE_SET = "VARIABLE CHART '{id}' = {value}\n"
|
|
|
|
RUNTIME_CHART_CREATE = "CHART netdata.runtime_{job_name} '' 'Execution time for {job_name}' 'ms' 'python.d' " \
|
|
"netdata.pythond_runtime line 145000 {update_every}\n" \
|
|
"DIMENSION run_time 'run time' absolute 1 1\n"
|
|
|
|
|
|
def create_runtime_chart(func):
|
|
"""
|
|
Calls a wrapped function, then prints runtime chart to stdout.
|
|
|
|
Used as a decorator for SimpleService.create() method.
|
|
The whole point of making 'create runtime chart' functionality as a decorator was
|
|
to help users who re-implements create() in theirs classes.
|
|
|
|
:param func: class method
|
|
:return:
|
|
"""
|
|
def wrapper(*args, **kwargs):
|
|
self = args[0]
|
|
ok = func(*args, **kwargs)
|
|
if ok:
|
|
safe_print(RUNTIME_CHART_CREATE.format(job_name=self.name,
|
|
update_every=self._runtime_counters.update_every))
|
|
return ok
|
|
return wrapper
|
|
|
|
|
|
class ChartError(Exception):
|
|
"""Base-class for all exceptions raised by this module"""
|
|
|
|
|
|
class DuplicateItemError(ChartError):
|
|
"""Occurs when user re-adds a chart or a dimension that has already been added"""
|
|
|
|
|
|
class ItemTypeError(ChartError):
|
|
"""Occurs when user passes value of wrong type to Chart, Dimension or ChartVariable class"""
|
|
|
|
|
|
class ItemValueError(ChartError):
|
|
"""Occurs when user passes inappropriate value to Chart, Dimension or ChartVariable class"""
|
|
|
|
|
|
class Charts:
|
|
"""Represent a collection of charts
|
|
|
|
All charts stored in a dict.
|
|
Chart is a instance of Chart class.
|
|
Charts adding must be done using Charts.add_chart() method only"""
|
|
def __init__(self, job_name, priority, cleanup, get_update_every, module_name):
|
|
"""
|
|
:param job_name: <bound method>
|
|
:param priority: <int>
|
|
:param get_update_every: <bound method>
|
|
"""
|
|
self.job_name = job_name
|
|
self.priority = priority
|
|
self.cleanup = cleanup
|
|
self.get_update_every = get_update_every
|
|
self.module_name = module_name
|
|
self.charts = dict()
|
|
|
|
def __len__(self):
|
|
return len(self.charts)
|
|
|
|
def __iter__(self):
|
|
return iter(self.charts.values())
|
|
|
|
def __repr__(self):
|
|
return 'Charts({0})'.format(self)
|
|
|
|
def __str__(self):
|
|
return str([chart for chart in self.charts])
|
|
|
|
def __contains__(self, item):
|
|
return item in self.charts
|
|
|
|
def __getitem__(self, item):
|
|
return self.charts[item]
|
|
|
|
def __delitem__(self, key):
|
|
del self.charts[key]
|
|
|
|
def __bool__(self):
|
|
return bool(self.charts)
|
|
|
|
def __nonzero__(self):
|
|
return self.__bool__()
|
|
|
|
def add_chart(self, params):
|
|
"""
|
|
Create Chart instance and add it to the dict
|
|
|
|
Manually adds job name, priority and update_every to params.
|
|
:param params: <list>
|
|
:return:
|
|
"""
|
|
params = [self.job_name()] + params
|
|
new_chart = Chart(params)
|
|
|
|
new_chart.params['update_every'] = self.get_update_every()
|
|
new_chart.params['priority'] = self.priority
|
|
new_chart.params['module_name'] = self.module_name
|
|
|
|
self.priority += 1
|
|
self.charts[new_chart.id] = new_chart
|
|
|
|
return new_chart
|
|
|
|
def active_charts(self):
|
|
return [chart.id for chart in self if not chart.flags.obsoleted]
|
|
|
|
|
|
class Chart:
|
|
"""Represent a chart"""
|
|
def __init__(self, params):
|
|
"""
|
|
:param params: <list>
|
|
"""
|
|
if not isinstance(params, list):
|
|
raise ItemTypeError("'chart' must be a list type")
|
|
if not len(params) >= 8:
|
|
raise ItemValueError("invalid value for 'chart', must be {0}".format(CHART_PARAMS))
|
|
|
|
self.params = dict(zip(CHART_PARAMS, (p or str() for p in params)))
|
|
self.name = '{type}.{id}'.format(type=self.params['type'],
|
|
id=self.params['id'])
|
|
if self.params.get('chart_type') not in CHART_TYPES:
|
|
self.params['chart_type'] = 'absolute'
|
|
hidden = str(self.params.get('hidden', ''))
|
|
self.params['hidden'] = 'hidden' if hidden == 'hidden' else ''
|
|
|
|
self.dimensions = list()
|
|
self.variables = set()
|
|
self.flags = ChartFlags()
|
|
self.penalty = 0
|
|
|
|
def __getattr__(self, item):
|
|
try:
|
|
return self.params[item]
|
|
except KeyError:
|
|
raise AttributeError("'{instance}' has no attribute '{attr}'".format(instance=repr(self),
|
|
attr=item))
|
|
|
|
def __repr__(self):
|
|
return 'Chart({0})'.format(self.id)
|
|
|
|
def __str__(self):
|
|
return self.id
|
|
|
|
def __iter__(self):
|
|
return iter(self.dimensions)
|
|
|
|
def __contains__(self, item):
|
|
return item in [dimension.id for dimension in self.dimensions]
|
|
|
|
def add_variable(self, variable):
|
|
"""
|
|
:param variable: <list>
|
|
:return:
|
|
"""
|
|
self.variables.add(ChartVariable(variable))
|
|
|
|
def add_dimension(self, dimension):
|
|
"""
|
|
:param dimension: <list>
|
|
:return:
|
|
"""
|
|
dim = Dimension(dimension)
|
|
|
|
if dim.id in self:
|
|
raise DuplicateItemError("'{dimension}' already in '{chart}' dimensions".format(dimension=dim.id,
|
|
chart=self.name))
|
|
self.refresh()
|
|
self.dimensions.append(dim)
|
|
return dim
|
|
|
|
def del_dimension(self, dimension_id, hide=True):
|
|
if dimension_id not in self:
|
|
return
|
|
idx = self.dimensions.index(dimension_id)
|
|
dimension = self.dimensions[idx]
|
|
if hide:
|
|
dimension.params['hidden'] = 'hidden'
|
|
dimension.params['obsolete'] = 'obsolete'
|
|
self.create()
|
|
self.dimensions.remove(dimension)
|
|
|
|
def hide_dimension(self, dimension_id, reverse=False):
|
|
if dimension_id not in self:
|
|
return
|
|
idx = self.dimensions.index(dimension_id)
|
|
dimension = self.dimensions[idx]
|
|
dimension.params['hidden'] = 'hidden' if not reverse else str()
|
|
self.refresh()
|
|
|
|
def create(self):
|
|
"""
|
|
:return:
|
|
"""
|
|
chart = CHART_CREATE.format(**self.params)
|
|
dimensions = ''.join([dimension.create() for dimension in self.dimensions])
|
|
variables = ''.join([var.set(var.value) for var in self.variables if var])
|
|
|
|
self.flags.push = False
|
|
self.flags.created = True
|
|
|
|
safe_print(chart + dimensions + variables)
|
|
|
|
def can_be_updated(self, data):
|
|
for dim in self.dimensions:
|
|
if dim.get_value(data) is not None:
|
|
return True
|
|
return False
|
|
|
|
def update(self, data, interval):
|
|
updated_dimensions, updated_variables = str(), str()
|
|
|
|
for dim in self.dimensions:
|
|
value = dim.get_value(data)
|
|
if value is not None:
|
|
updated_dimensions += dim.set(value)
|
|
|
|
for var in self.variables:
|
|
value = var.get_value(data)
|
|
if value is not None:
|
|
updated_variables += var.set(value)
|
|
|
|
if updated_dimensions:
|
|
since_last = interval if self.flags.updated else 0
|
|
|
|
if self.flags.push:
|
|
self.create()
|
|
|
|
chart_begin = CHART_BEGIN.format(type=self.type, id=self.id, since_last=since_last)
|
|
safe_print(chart_begin, updated_dimensions, updated_variables, 'END\n')
|
|
|
|
self.flags.updated = True
|
|
self.penalty = 0
|
|
else:
|
|
self.penalty += 1
|
|
self.flags.updated = False
|
|
|
|
return bool(updated_dimensions)
|
|
|
|
def obsolete(self):
|
|
self.flags.obsoleted = True
|
|
if self.flags.created:
|
|
safe_print(CHART_OBSOLETE.format(**self.params))
|
|
|
|
def refresh(self):
|
|
self.penalty = 0
|
|
self.flags.push = True
|
|
self.flags.obsoleted = False
|
|
|
|
|
|
class Dimension:
|
|
"""Represent a dimension"""
|
|
def __init__(self, params):
|
|
"""
|
|
:param params: <list>
|
|
"""
|
|
if not isinstance(params, list):
|
|
raise ItemTypeError("'dimension' must be a list type")
|
|
if not params:
|
|
raise ItemValueError("invalid value for 'dimension', must be {0}".format(DIMENSION_PARAMS))
|
|
|
|
self.params = dict(zip(DIMENSION_PARAMS, (p or str() for p in params)))
|
|
self.params['name'] = self.params.get('name') or self.params['id']
|
|
|
|
if self.params.get('algorithm') not in DIMENSION_ALGORITHMS:
|
|
self.params['algorithm'] = 'absolute'
|
|
if not isinstance(self.params.get('multiplier'), int):
|
|
self.params['multiplier'] = 1
|
|
if not isinstance(self.params.get('divisor'), int):
|
|
self.params['divisor'] = 1
|
|
self.params.setdefault('hidden', '')
|
|
self.params.setdefault('obsolete', '')
|
|
|
|
def __getattr__(self, item):
|
|
try:
|
|
return self.params[item]
|
|
except KeyError:
|
|
raise AttributeError("'{instance}' has no attribute '{attr}'".format(instance=repr(self),
|
|
attr=item))
|
|
|
|
def __repr__(self):
|
|
return 'Dimension({0})'.format(self.id)
|
|
|
|
def __str__(self):
|
|
return self.id
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, Dimension):
|
|
return self.id == other
|
|
return self.id == other.id
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
def __hash__(self):
|
|
return hash(repr(self))
|
|
|
|
def create(self):
|
|
return DIMENSION_CREATE.format(**self.params)
|
|
|
|
def set(self, value):
|
|
"""
|
|
:param value: <str>: must be a digit
|
|
:return:
|
|
"""
|
|
return DIMENSION_SET.format(id=self.id,
|
|
value=value)
|
|
|
|
def get_value(self, data):
|
|
try:
|
|
return int(data[self.id])
|
|
except (KeyError, TypeError):
|
|
return None
|
|
|
|
|
|
class ChartVariable:
|
|
"""Represent a chart variable"""
|
|
def __init__(self, params):
|
|
"""
|
|
:param params: <list>
|
|
"""
|
|
if not isinstance(params, list):
|
|
raise ItemTypeError("'variable' must be a list type")
|
|
if not params:
|
|
raise ItemValueError("invalid value for 'variable' must be: {0}".format(VARIABLE_PARAMS))
|
|
|
|
self.params = dict(zip(VARIABLE_PARAMS, params))
|
|
self.params.setdefault('value', None)
|
|
|
|
def __getattr__(self, item):
|
|
try:
|
|
return self.params[item]
|
|
except KeyError:
|
|
raise AttributeError("'{instance}' has no attribute '{attr}'".format(instance=repr(self),
|
|
attr=item))
|
|
|
|
def __bool__(self):
|
|
return self.value is not None
|
|
|
|
def __nonzero__(self):
|
|
return self.__bool__()
|
|
|
|
def __repr__(self):
|
|
return 'ChartVariable({0})'.format(self.id)
|
|
|
|
def __str__(self):
|
|
return self.id
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, ChartVariable):
|
|
return self.id == other.id
|
|
return False
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
def __hash__(self):
|
|
return hash(repr(self))
|
|
|
|
def set(self, value):
|
|
return CHART_VARIABLE_SET.format(id=self.id,
|
|
value=value)
|
|
|
|
def get_value(self, data):
|
|
try:
|
|
return int(data[self.id])
|
|
except (KeyError, TypeError):
|
|
return None
|
|
|
|
|
|
class ChartFlags:
|
|
def __init__(self):
|
|
self.push = True
|
|
self.created = False
|
|
self.updated = False
|
|
self.obsoleted = False
|