-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathperflog.py
118 lines (90 loc) · 3.57 KB
/
perflog.py
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
""" Performance stats logger """
import sys
import time
import logging
import json
from collections import Counter
import threading
import typing
import enum
import psutil
__version__ = '2017.8.4'
PY_VERSION = (sys.version_info.major, sys.version_info.minor)
ASYNCIO_ALLOWED = PY_VERSION >= (3, 5)
if typing.TYPE_CHECKING: # pragma: no cover
from typing import NamedTuple, FrozenSet
logger = logging.getLogger('perflog')
@enum.unique
class PerfSection(enum.IntEnum):
CPU = 1
MEM = 2
NET = 3
NET_DETAIL = 4
def _perf_log(message, nt, level=logging.INFO, field_prefix=''):
# type: (str, NamedTuple, int, str) -> None
# Use a field prefix to avoid collisions with the logrecord fields.
d = {field_prefix + k: v for k, v in nt._asdict().items()}
logger.log(level, message, json.dumps(d, indent=2), extra=d)
def single_pass(process=None, sections=frozenset(PerfSection)):
# type: (psutil.Process, FrozenSet[PerfSection]) -> None
process = process or psutil.Process()
if PerfSection.CPU in sections:
cpu_times = process.cpu_times()
_perf_log('CPU times = %s', cpu_times, field_prefix='cpu_')
cpu_percent = process.cpu_percent(interval=1)
logger.info(
'CPU Percent = %s',
cpu_percent,
extra=dict(cpu_percent=cpu_percent)
)
if PerfSection.MEM in sections:
vmem = psutil.virtual_memory()
_perf_log('Virtual memory = %s', vmem, field_prefix='vmem_')
mem_info = process.memory_full_info()
_perf_log('Proc memory info = %s', mem_info, field_prefix='mem_')
if PerfSection.NET in sections:
net_info = psutil.net_io_counters()
_perf_log('Network info = %s', net_info, field_prefix='net_')
conn_info = process.connections()
logger.info(
'Total Network connections = %d', len(conn_info),
extra=dict(tot_connections=len(conn_info))
)
if PerfSection.NET_DETAIL in sections:
# Counter on remote address & port tuples
counter = Counter(c.raddr for c in conn_info)
logger.info(
'Connections to remote addresses = %s',
counter
)
if len(conn_info) <= 10:
for conn in conn_info:
_perf_log('Detailed connection info = %s', conn, field_prefix='conn_')
def multi_pass_blocking(process=None, sections=frozenset(PerfSection), interval=60.0):
# type: (psutil.Process, FrozenSet[PerfSection], float) -> None
process = process or psutil.Process()
while True:
try:
single_pass(process, sections)
except: # pragma: no cover
logger.exception('Problem logging perf stats:')
finally:
time.sleep(interval)
def set_and_forget(process=None, sections=frozenset(PerfSection), interval=60.0):
# type: (psutil.Process, FrozenSet[PerfSection], float) -> None
""" Creates a daemon thread that simply logs perf stats forever. This is
obviously also compatible with asyncio, since it runs in a thread. """
process = process or psutil.Process()
thread = threading.Thread(
target=multi_pass_blocking,
name='perflog',
args=(),
kwargs=dict(process=process, sections=sections),
)
thread.daemon = True
thread.start()
################################################################################
if ASYNCIO_ALLOWED:
# We have to do this because we are trying to be Python 2 compatible, but
# still allow the use of async def for Python 3.5+.
from perflog_async import single_pass_async, multi_pass_async