Commit 2d652efb authored by Tristan Cavelier's avatar Tristan Cavelier

erp5: add mariadb slowquery promise

parent 9653d146
......@@ -2,6 +2,15 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"required": ["tcpv4-port"],
"properties": {
"monitor": {
"description": "Add a web monitoring interface + access to logs",
"default": false,
"type": "boolean"
},
"monitor-port": {
"description": "Set the monitor port",
"type": "interger"
},
"tcpv4-port": {
"allOf": [{
"$ref": "#/definitions/tcpv4port"
......
......@@ -50,6 +50,8 @@ extends =
../../component/findutils/buildout.cfg
../../component/userhosts/buildout.cfg
../../component/postfix/buildout.cfg
# XXX line to comment, the monitor should be extended from software.cfg
../../stack/monitor/buildout.cfg
../../software/neoppod/software-common.cfg
# keep neoppod extends last
......@@ -144,7 +146,7 @@ mode = 755
[template-mariadb]
<= download-base
filename = instance-mariadb.cfg.in
md5sum = fbc39d333bf70894f6f9d094515a2a4a
md5sum = f61c71a48d444fccb3fb8e7a15469427
link-binary =
${coreutils:location}/bin/basename
${coreutils:location}/bin/cat
......@@ -157,6 +159,23 @@ link-binary =
${sed:location}/bin/sed
${mariadb:location}/bin/mysqlbinlog
[template-mariadb-slowquery-promise-cfg]
<= download-base
filename = mariadb_slowquery_promise.cfg.in
md5sum = 5913d2a0096b50537f394a49b762b3e5
[template-mariadb-slowquery-promise-interface]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/template/mariadb-slowquery-promise-interface.html.in
md5sum = bcee2a70e5b8db2059e48ba35cc0bbd9
mode = 644
[template-mariadb-slowquery-promise-script]
<= download-base
filename = mariadb_slowquery_promise.py.in
md5sum = bc043435dc93bf9700a4a50552d6dc4d
mode = 755
[template-kumofs]
<= download-base
filename = instance-kumofs.cfg.in
......@@ -222,9 +241,10 @@ recipe = slapos.recipe.template:jinja2
# XXX: "template.cfg" is hardcoded in instanciation recipe
rendered = ${buildout:directory}/template.cfg
template = ${:_profile_base_location_}/instance.cfg.in
md5sum = 540956c635acc9707045510c11f80016
md5sum = 4601851a4b4efb760b52ad9ceac2b532
mode = 640
context =
raw python_executable ${buildout:executable}
key mariadb_link_binary template-mariadb:link-binary
key zope_link_binary template-zope:link-binary
key apache_location apache:location
......@@ -288,6 +308,9 @@ context =
key template_kumofs template-kumofs:target
key template_mariadb template-mariadb:target
key template_mariadb_initial_setup template-mariadb-initial-setup:target
key template_mariadb_slowquery_promise_cfg template-mariadb-slowquery-promise-cfg:target
key template_mariadb_slowquery_promise_interface template-mariadb-slowquery-promise-interface:target
key template_mariadb_slowquery_promise_script template-mariadb-slowquery-promise-script:target
key template_monitor monitor-template:rendered
key template_my_cnf template-my-cnf:target
key template_postfix template-postfix:target
......
......@@ -11,6 +11,8 @@
{% set full_backup_retention_days = catalog_backup.get('full-retention-days', 7) -%}
{% set incremental_backup_retention_days = catalog_backup.get('incremental-retention-days', full_backup_retention_days) -%}
{% set port = slapparameter_dict['tcpv4-port'] %}
{% set use_monitor = slapparameter_dict.get("monitor", True) -%}
{% set monitor_port = slapparameter_dict.get("monitor-port", port + 1) -%}
{% if use_ipv6 -%}
{% set ip = (ipv6_set | list)[0] -%}
{% else -%}
......@@ -32,6 +34,9 @@ recipe = slapos.cookbook:publish.serialised
{% endmacro -%}
database-list = {{ render_database_list(database_list) }}
test-database-list = {{ render_database_list(test_database_list) }}
{% if use_monitor -%}
monitor-url = ${monitor-publish:monitor_url_v6}
{% endif -%}
[simplefile]
recipe = slapos.recipe.template:jinja2
......@@ -232,15 +237,49 @@ command-line = "{{ parameter_dict['bin-directory'] }}/is-local-tcp-port-opened"
wrapper-path = ${directory:promise}/mariadb
parameters-extra = true
{% if use_monitor -%}
[slowquery-promise-cfg-parameter]
title = Mariadb slow queries
name = mariadb-slow-query
# frequency minute hour day mounth weekday
frequency = */5 * * * *
promise-path = ${slowquery-promise-script:rendered}
interface-path = ${monitor-directory:web-dir}/${:name}.html
public-path-list = ${my-cnf-parameters:slow-query-log}
private-path-list =
[{{ section("slowquery-promise-cfg") }}]
recipe = slapos.recipe.template:jinja2
rendered = ${monitor-directory:services-conf}/mariadb_slowquery_promise.cfg
template = {{ parameter_dict['template-mariadb-slowquery-promise-cfg'] }}
context = section parameter_dict slowquery-promise-cfg-parameter
[slowquery-promise-interface-parameter]
[{{ section("slowquery-promise-interface") }}]
recipe = slapos.recipe.template:jinja2
rendered = ${slowquery-promise-cfg-parameter:interface-path}
template = {{ parameter_dict['template-mariadb-slowquery-promise-interface'] }}
context = section parameter_dict slowquery-promise-interface-parameter
[{{ section("slowquery-promise-script") }}]
recipe = slapos.recipe.template:jinja2
rendered = ${directory:bin}/mariadb_slowquery_promise.py
template = {{ parameter_dict['template-mariadb-slowquery-promise-script'] }}
context =
raw python_executable {{ python_executable }}
key mariadb_slowquery_log_path my-cnf-parameters:slow-query-log
[monitor-instance-parameter]
monitor-httpd-ipv6 = {{ (ipv6_set | list)[0] }}
monitor-httpd-port = {{ port + 1 }}
monitor-httpd-port = {{ monitor_port }}
monitor-title = Mariadb monitor
{% endif -%}{# use_monitor #}
[buildout]
extends =
{{ logrotate_cfg }}
{{ parameter_dict['template-monitor'] }}
{% if use_monitor %}{{ parameter_dict['template-monitor'] }}{% endif %}
parts +=
publish
logrotate-entry-mariadb
......
......@@ -152,6 +152,9 @@ gzip-location = {{ gzip_location }}
mariadb-location = {{ mariadb_location }}
template-my-cnf = {{ template_my_cnf }}
template-mariadb-initial-setup = {{ template_mariadb_initial_setup }}
template-mariadb-slowquery-promise-cfg = {{ template_mariadb_slowquery_promise_cfg }}
template-mariadb-slowquery-promise-interface = {{ template_mariadb_slowquery_promise_interface }}
template-mariadb-slowquery-promise-script = {{ template_mariadb_slowquery_promise_script }}
link-binary = {{ dumps(mariadb_link_binary) }}
bin-directory = {{ bin_directory }}
mariadb-resiliency-after-import-script = {{ mariadb_resiliency_after_import_script }}
......@@ -162,6 +165,8 @@ template-monitor = {{ template_monitor }}
template = {{ template_mariadb }}
filename = instance-mariadb.cfg
extra-context =
raw python_executable {{ python_executable }}
key buildout_directory buildout:directory
section parameter_dict dynamic-template-mariadb-parameters
[dynamic-template-create-erp5-site-parameters]
......
[service]
{% for key, value in parameter_dict.items() -%}
{{ key }} = {{ value.strip().replace("\n", "\n ") }}
{% endfor -%}
#!{{ python_executable }}
mariadb_slowquery_log_path = "{{ mariadb_slowquery_log_path }}"
max_count = 300
import sys
import errno
def main():
count = 0
try:
for line in open(mariadb_slowquery_log_path, "rb"):
if line.startswith("# Query_time: "):
count += 1
except IOError as e:
if e.errno != errno.ENOENT:
raise
print('{"status":"error","message":"No log found","description":"Slow queries amount before bad status: %s"}' % max_count)
return 2
if count > max_count:
print('{"status":"bad","message":"%s slowqueries today","description":"Slow queries amount before bad status: %s"}' % (count, max_count))
return 1
print('{"status":"OK","message":"%s slowqueries today","description":"Slow queries amount before bad status: %s"}' % (count, max_count))
return 0
if __name__ == "__main__":
sys.exit(main());
<!DOCTYPE html>
<html>
<head>
<title>Mariadb slowquery</title>
<link rel="stylesheet" href="monitor.css" />
<script>
var mariadb_slowquery_log_url = "/public/mariadb-slow-query/mariadb_slowquery.log", // XXX hardcoded
mariadb_slowquery_log_basename = "mariadb_slowquery.log",
mariadb_status_json_url = "/public/mariadb-slow-query/status.json", // XXX hardcoded
consoleError = console.error.bind(console);
function newDeferred() {
var d = {
"promise": undefined,
"resolve": undefined,
"reject": undefined
};
d.promise = new Promise(function (resolve, reject) {
d.resolve = resolve;
d.reject = reject;
});
return d;
}
function xhr(param) {
/*global XMLHttpRequest */
var d = newDeferred(), xhr = new XMLHttpRequest(), k, i, l, a;
d.promise.cancel = function () { xhr.abort(); };
xhr.open((param.method || "GET").toUpperCase(), param.url, true);
xhr.responseType = param.responseType || "";
if (param.withCredentials !== undefined) {
xhr.withCredentials = param.withCredentials;
}
if (param.headers) {
a = Object.keys(param.headers);
l = a.length;
for (i = 0; i < l; i += 1) {
k = a[i];
xhr.setRequestHeader(k, param.headers[k]);
}
}
xhr.addEventListener("load", function (e) {
var r, t = e.target, callback;
if (param.noStatusCheck) {
d.resolve(t);
} else if (t.status < 400) {
d.resolve(t);
} else {
d.reject(new Error("HTTP: " + (t.status ? t.status + " " : "") + (t.statusText || "Unknown")));
}
}, false);
xhr.addEventListener("error", function (e) {
return d.reject(new Error("HTTP: Error"));
}, false);
xhr.addEventListener("abort", function (e) {
return d.reject(new Error("HTTP: Aborted"));
}, false);
xhr.send(param.data);
return d.promise;
}
///////////////////
// tools for HAL //
function getProperty(object, path) {
if (Array.isArray(path)) {
while (path.length) {
object = object[path.shift()];
}
} else {
return object[path];
}
return object;
}
function softGetProperty(object, path) {
try {
return getProperty(object, path);
} catch (ignored) {
return undefined;
}
}
function forceList(value) {
if (Array.isArray(value)) {
return value;
}
return [value];
}
function softGetPropertyAsList(object, path) {
try {
return forceList(getProperty(object, path));
} catch (ignored) {
return [];
}
}
///////////////////
function htmlToElementList(html) {
/*global document */
var div = document.createElement("div");
div.innerHTML = html;
return div.querySelectorAll("*");
}
function resolveUrl(firstUrl) {
/*jslint plusplus: true */
/*global URL, location */
var l = arguments.length, i = 1, url = new URL(firstUrl, location.href);
while (i < l) { url = new URL(arguments[i++], url); }
return url.href;
}
function escapeHtml(html) {
return html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}
function loadLastSlowQueries() {
var range = 0, length = 0, slowqueries = [];
return xhr({"url": mariadb_slowquery_log_url, "method": "HEAD"}).then(function (response) {
length = parseInt(response.getResponseHeader("Content-Length"), 10);
range = Math.max(length - 8192, 0); // 8Ki
return xhr({
"url": mariadb_slowquery_log_url,
"method": "GET",
"headers": {"Range": "bytes=" + range + "-" + length},
"responseType": "text"
});
}).then(function (response) {
var match = (/^# (Time: [0-9]+|User@Host: )/m).exec(response.responseText);
if (match) {
return [range + match.index, response.responseText.slice(match.index)];
}
return [range, response.responseText];
});
}
function loadAndRenderStatus(root) {
var element_list = htmlToElementList([
"<pre></pre>"
].join("\n")), pre = element_list[0];
return xhr({"url": mariadb_status_json_url, "method": "GET", "responseType": "json", "noStatusCheck": true}).then(function (response) {
if (response.status < 400) {
pre.innerHTML = "<p>" + escapeHtml(response.response.message || "No status message") + "</p>" +
(response.response.description ? "<p>" + escapeHtml(response.response.description) + "</p>" : "");
} else {
if (response.status === 404) {
pre.textContent = "Cannot retreive the status of this promise. Please wait for the promise to be ran.";
} else {
pre.textContent = "Cannot retreive the status of this promise.";
}
}
root.appendChild(pre);
}).catch(function (reason) {
if (reason instanceof Error) {
root.textContent = reason.name + ": " + reason.message;
consoleError(reason);
} else {
root.textContent = "Error: " + reason;
}
});
}
function loadAndRenderLastLog(root) {
var elements = htmlToElementList("<p><a class=\"as-button\">Download</a></p><p>Loading end of last log...</p>"), range = 0, a = elements[1];
a.download = mariadb_slowquery_log_basename;
a.href = mariadb_slowquery_log_url;
root.appendChild(elements[0]);
root.appendChild(elements[2]);
return loadLastSlowQueries().then(function (args) {
elements[2].remove();
if (args[0] !== 0) {
root.appendChild(htmlToElementList("<p><i>[...]</i></p>")[0]);
}
var el = document.createElement("pre");
el.textContent = args[1];
root.appendChild(el);
}).catch(function (reason) {
if (reason instanceof Error) {
root.textContent = reason.name + ": " + reason.message;
consoleError(reason);
} else {
root.textContent = "Error: " + reason;
}
});
}
function bootstrap(root) {
var element_list = htmlToElementList([
"<header><a href=\"/\" class=\"as-button\">Home</a> <a href=\"\" class=\"as-button\">Refresh</a></header>",
"<h1>Mariadb slowqueries</h1>",
"<h2 id=\"status\">Status</h2>",
"<div></div>", // 5
"<h2 id=\"last-slow-queries\">Last slow queries</h2>",
"<div></div>" // 7
].join("\n")), status_div = element_list[5], last_log_div = element_list[7], tmp;
[].forEach.call(element_list, function (element) {
if (element.parentNode.parentNode) { return; }
root.appendChild(element);
});
tmp = loadAndRenderStatus(status_div);
tmp.catch(consoleError);
tmp = loadAndRenderLastLog(last_log_div);
tmp.catch(consoleError);
}
/*global setTimeout */
setTimeout(function () {
/*global document */
document.body.innerHTML = "";
bootstrap(document.body);
});
</script>
</head>
<body>
<h1>Mariadb slowqueries</h1>
<noscript>Javascript should be enabled</noscript>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment