127 lines
3.2 KiB
Python
Executable File
127 lines
3.2 KiB
Python
Executable File
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
import json
|
|
import logging
|
|
import os
|
|
import platform
|
|
import socket
|
|
import threading
|
|
import time
|
|
from collections import namedtuple
|
|
from contextlib import contextmanager
|
|
|
|
from trytond.config import config
|
|
|
|
status = dict()
|
|
logger = logging.getLogger(__name__)
|
|
address = 'trytond-stat.socket'
|
|
|
|
Process = namedtuple('Process', ['start_time', 'request'])
|
|
|
|
_PID = None
|
|
_LOCK = threading.Lock()
|
|
|
|
|
|
@contextmanager
|
|
def processing(request):
|
|
start() # check if running thread
|
|
process = Process(time.perf_counter(), request)
|
|
status[id(process)] = process
|
|
try:
|
|
yield
|
|
finally:
|
|
status.pop(id(process), None)
|
|
|
|
|
|
def log():
|
|
from trytond.cache import Cache
|
|
msg = []
|
|
now = time.perf_counter()
|
|
for process in sorted(status.copy().values(), key=lambda p: p.start_time):
|
|
msg.append({
|
|
'since': now - process.start_time,
|
|
'request': str(process.request),
|
|
})
|
|
return {
|
|
'id': '%s@%s' % (os.getpid(), platform.node()),
|
|
'status': msg,
|
|
'caches': list(Cache.stats()),
|
|
}
|
|
|
|
|
|
def dump(path):
|
|
if not hasattr(socket, 'AF_UNIX'):
|
|
return False
|
|
sock = socket.socket(socket.AF_UNIX)
|
|
try:
|
|
try:
|
|
sock.connect(os.path.join(path, address))
|
|
except socket.error:
|
|
return False
|
|
try:
|
|
sock.sendall(json.dumps(log()).encode('utf-8'))
|
|
except socket.error:
|
|
pass
|
|
finally:
|
|
sock.close()
|
|
return True
|
|
|
|
|
|
def dumper(path):
|
|
while True:
|
|
if dump(path):
|
|
time.sleep(5)
|
|
else:
|
|
time.sleep(60)
|
|
|
|
|
|
def start():
|
|
global _PID
|
|
if not hasattr(socket, 'AF_UNIX'):
|
|
return
|
|
if _PID != os.getpid(): # Quick test without lock
|
|
with _LOCK:
|
|
if _PID != os.getpid():
|
|
path = config.get('database', 'path')
|
|
threading.Thread(
|
|
target=dumper, args=(path,), daemon=True).start()
|
|
_PID = os.getpid()
|
|
|
|
|
|
def listen(path, callback=None):
|
|
if not hasattr(socket, 'AF_UNIX'):
|
|
return False
|
|
sock = socket.socket(socket.AF_UNIX)
|
|
socket_file = os.path.join(path, address)
|
|
try:
|
|
sock.bind(socket_file)
|
|
sock.listen(1)
|
|
sock.settimeout(5)
|
|
while True:
|
|
try:
|
|
connection, client_address = sock.accept()
|
|
except socket.timeout:
|
|
if callback:
|
|
callback()
|
|
continue
|
|
try:
|
|
data = b''
|
|
while True:
|
|
chunk = connection.recv(1024)
|
|
if not chunk:
|
|
break
|
|
else:
|
|
data += chunk
|
|
try:
|
|
data = json.loads(data)
|
|
except ValueError:
|
|
continue
|
|
if callback:
|
|
callback(data)
|
|
finally:
|
|
connection.close()
|
|
finally:
|
|
sock.close()
|
|
os.unlink(socket_file)
|
|
return True
|