1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
# -*- coding: utf-8 -*-
# Description: uwsgi netdata python.d module
# Author: Robbert Segeren (robbert-ef)
# SPDX-License-Identifier: GPL-3.0-or-later
import json
from copy import deepcopy
from bases.FrameworkServices.SocketService import SocketService
ORDER = [
'requests',
'tx',
'avg_rt',
'memory_rss',
'memory_vsz',
'exceptions',
'harakiri',
'respawn',
]
DYNAMIC_CHARTS = [
'requests',
'tx',
'avg_rt',
'memory_rss',
'memory_vsz',
]
# NOTE: lines are created dynamically in `check()` method
CHARTS = {
'requests': {
'options': [None, 'Requests', 'requests/s', 'requests', 'uwsgi.requests', 'stacked'],
'lines': [
['requests', 'requests', 'incremental']
]
},
'tx': {
'options': [None, 'Transmitted data', 'KiB/s', 'requests', 'uwsgi.tx', 'stacked'],
'lines': [
['tx', 'tx', 'incremental']
]
},
'avg_rt': {
'options': [None, 'Average request time', 'milliseconds', 'requests', 'uwsgi.avg_rt', 'line'],
'lines': [
['avg_rt', 'avg_rt', 'absolute']
]
},
'memory_rss': {
'options': [None, 'RSS (Resident Set Size)', 'MiB', 'memory', 'uwsgi.memory_rss', 'stacked'],
'lines': [
['memory_rss', 'memory_rss', 'absolute', 1, 1 << 20]
]
},
'memory_vsz': {
'options': [None, 'VSZ (Virtual Memory Size)', 'MiB', 'memory', 'uwsgi.memory_vsz', 'stacked'],
'lines': [
['memory_vsz', 'memory_vsz', 'absolute', 1, 1 << 20]
]
},
'exceptions': {
'options': [None, 'Exceptions', 'exceptions', 'exceptions', 'uwsgi.exceptions', 'line'],
'lines': [
['exceptions', 'exceptions', 'incremental']
]
},
'harakiri': {
'options': [None, 'Harakiris', 'harakiris', 'harakiris', 'uwsgi.harakiris', 'line'],
'lines': [
['harakiri_count', 'harakiris', 'incremental']
]
},
'respawn': {
'options': [None, 'Respawns', 'respawns', 'respawns', 'uwsgi.respawns', 'line'],
'lines': [
['respawn_count', 'respawns', 'incremental']
]
},
}
class Service(SocketService):
def __init__(self, configuration=None, name=None):
super(Service, self).__init__(configuration=configuration, name=name)
self.order = ORDER
self.definitions = deepcopy(CHARTS)
self.url = self.configuration.get('host', 'localhost')
self.port = self.configuration.get('port', 1717)
# Clear dynamic dimensions, these are added during `_get_data()` to allow adding workers at run-time
for chart in DYNAMIC_CHARTS:
self.definitions[chart]['lines'] = []
self.last_result = {}
self.workers = []
def read_data(self):
"""
Read data from socket and parse as JSON.
:return: (dict) stats
"""
raw_data = self._get_raw_data()
if not raw_data:
return None
try:
return json.loads(raw_data)
except ValueError as err:
self.error(err)
return None
def check(self):
"""
Parse configuration and check if we can read data.
:return: boolean
"""
self._parse_config()
return bool(self.read_data())
def add_worker_dimensions(self, key):
"""
Helper to add dimensions for a worker.
:param key: (int or str) worker identifier
:return:
"""
for chart in DYNAMIC_CHARTS:
for line in CHARTS[chart]['lines']:
dimension_id = '{}_{}'.format(line[0], key)
dimension_name = str(key)
dimension = [dimension_id, dimension_name] + line[2:]
self.charts[chart].add_dimension(dimension)
@staticmethod
def _check_raw_data(data):
# The server will close the connection when it's done sending
# data, so just keep looping until that happens.
return False
def _get_data(self):
"""
Read data from socket
:return: dict
"""
stats = self.read_data()
if not stats:
return None
result = {
'exceptions': 0,
'harakiri_count': 0,
'respawn_count': 0,
}
for worker in stats['workers']:
key = worker['pid']
# Add dimensions for new workers
if key not in self.workers:
self.add_worker_dimensions(key)
self.workers.append(key)
result['requests_{}'.format(key)] = worker['requests']
result['tx_{}'.format(key)] = worker['tx']
result['avg_rt_{}'.format(key)] = worker['avg_rt']
# avg_rt is not reset by uwsgi, so reset here
if self.last_result.get('requests_{}'.format(key)) == worker['requests']:
result['avg_rt_{}'.format(key)] = 0
result['memory_rss_{}'.format(key)] = worker['rss']
result['memory_vsz_{}'.format(key)] = worker['vsz']
result['exceptions'] += worker['exceptions']
result['harakiri_count'] += worker['harakiri_count']
result['respawn_count'] += worker['respawn_count']
self.last_result = result
return result
|