.BANCHO. Add check for MySQL connections pool saturation

This commit is contained in:
Nyo 2016-09-16 18:21:34 +02:00
parent 6020f7cc47
commit f912f6ea82
4 changed files with 51 additions and 7 deletions

View File

@ -1,14 +1,16 @@
import MySQLdb import MySQLdb
import threading import threading
import glob
from helpers import logHelper as log from helpers import logHelper as log
import threading
class mysqlWorker: class mysqlWorker:
""" """
Instance of a mysql worker Instance of a pettirosso meme
""" """
def __init__(self, wid, host, username, password, database): def __init__(self, wid, host, username, password, database):
""" """
Create a mysql worker Create a pettirosso meme (mysql worker)
wid -- worker id wid -- worker id
host -- hostname host -- hostname
@ -26,6 +28,7 @@ class db:
""" """
A MySQL db connection with multiple workers A MySQL db connection with multiple workers
""" """
def __init__(self, host, username, password, database, workers): def __init__(self, host, username, password, database, workers):
""" """
Create MySQL workers aka pettirossi meme Create MySQL workers aka pettirossi meme
@ -39,10 +42,24 @@ class db:
self.workers = [] self.workers = []
self.lastWorker = 0 self.lastWorker = 0
self.workersNumber = workers self.workersNumber = workers
self.locked = 0
for i in range(0,self.workersNumber): for i in range(0,self.workersNumber):
print(".", end="") print(".", end="")
self.workers.append(mysqlWorker(i, host, username, password, database)) self.workers.append(mysqlWorker(i, host, username, password, database))
def checkPoolSaturation(self):
"""
Check the number of busy connections in connections pool.
If the pool is 100% busy, log a message to sentry
"""
if self.locked >= (self.workersNumber-1):
msg = "MySQL connections pool is saturated!".format(self.locked, self.workersNumber)
log.warning(msg)
glob.application.sentry_client.captureMessage(msg, level="warning", extra={
"workersBusy": self.locked,
"workersTotal": self.workersNumber
})
def getWorker(self): def getWorker(self):
""" """
Return a worker object (round-robin way) Return a worker object (round-robin way)
@ -53,6 +70,10 @@ class db:
self.lastWorker = 0 self.lastWorker = 0
else: else:
self.lastWorker += 1 self.lastWorker += 1
# Saturation check
threading.Thread(target=self.checkPoolSaturation).start()
self.locked += 1
return self.workers[self.lastWorker] return self.workers[self.lastWorker]
def execute(self, query, params = ()): def execute(self, query, params = ()):
@ -77,8 +98,9 @@ class db:
if cursor: if cursor:
cursor.close() cursor.close()
worker.lock.release() worker.lock.release()
self.locked -= 1
def fetch(self, query, params = (), all_ = False): def fetch(self, query, params = (), all = False):
""" """
Fetch a single value from db that matches given query Fetch a single value from db that matches given query
@ -95,7 +117,7 @@ class db:
# Create cursor, execute the query and fetch one/all result(s) # Create cursor, execute the query and fetch one/all result(s)
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor) cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(query, params) cursor.execute(query, params)
if all_: if all == True:
return cursor.fetchall() return cursor.fetchall()
else: else:
return cursor.fetchone() return cursor.fetchone()
@ -104,6 +126,7 @@ class db:
if cursor: if cursor:
cursor.close() cursor.close()
worker.lock.release() worker.lock.release()
self.locked -= 1
def fetchAll(self, query, params = ()): def fetchAll(self, query, params = ()):
""" """

View File

@ -3,6 +3,8 @@ import tornado.web
import tornado.gen import tornado.gen
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from objects import glob from objects import glob
import threading
from helpers import logHelper as log
class asyncRequestHandler(tornado.web.RequestHandler): class asyncRequestHandler(tornado.web.RequestHandler):
""" """
@ -54,8 +56,25 @@ def runBackground(data, callback):
""" """
func, args, kwargs = data func, args, kwargs = data
def _callback(result): def _callback(result):
#glob.busyThreads -= 1
IOLoop.instance().add_callback(lambda: callback(result)) IOLoop.instance().add_callback(lambda: callback(result))
glob.pool.apply_async(func, args, kwargs, _callback) glob.pool.apply_async(func, args, kwargs, _callback)
#threading.Thread(target=checkPoolSaturation).start()
#glob.busyThreads += 1
def checkPoolSaturation():
"""
Check the number of busy threads in connections pool.
If the pool is 100% busy, log a message to sentry
"""
size = int(glob.conf.config["server"]["threads"])
if glob.busyThreads >= size:
msg = "Connections threads pool is saturated!"
log.warning(msg)
glob.application.sentry_client.captureMessage(msg, level="warning", extra={
"workersBusy": glob.busyThreads,
"workersTotal": size
})
def checkArguments(arguments, requiredArguments): def checkArguments(arguments, requiredArguments):
""" """

View File

@ -15,6 +15,7 @@ try:
except: except:
VERSION = "¯\_(xd)_/¯" VERSION = "¯\_(xd)_/¯"
application = None
db = None db = None
conf = None conf = None
banchoConf = None banchoConf = None
@ -29,6 +30,7 @@ cloudflare = False
chatFilters = None chatFilters = None
userIDCache = {} userIDCache = {}
pool = None pool = None
busyThreads = 0
debug = False debug = False
outputRequestTime = False outputRequestTime = False

6
pep.py
View File

@ -174,13 +174,13 @@ if __name__ == "__main__":
consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW) consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW)
# Make app # Make app
application = make_app() glob.application = make_app()
# Set up sentry # Set up sentry
try: try:
glob.sentry = generalFunctions.stringToBool(glob.conf.config["sentry"]["enable"]) glob.sentry = generalFunctions.stringToBool(glob.conf.config["sentry"]["enable"])
if glob.sentry: if glob.sentry:
application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION) glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
else: else:
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW) consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
except: except:
@ -214,7 +214,7 @@ if __name__ == "__main__":
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
# Start tornado # Start tornado
application.listen(serverPort) glob.application.listen(serverPort)
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()
finally: finally:
system.dispose() system.dispose()