summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergei Petrunia <psergey@askmonty.org>2017-07-15 22:23:02 +0200
committerSergei Petrunia <psergey@askmonty.org>2017-07-15 22:23:02 +0200
commitc0930fe26871fde7e3cc60c4a209c5856fe878a1 (patch)
tree47fea0f8c854b182facce188555ba5835050f293
parent9b4d281ecd01b0d1d4a55dfaff5f27720037cb77 (diff)
downloadmariadb-git-c0930fe26871fde7e3cc60c4a209c5856fe878a1.tar.gz
MDEV-13330: ANALYZE FORMAT=JSON should print time spent in SPs
-rw-r--r--mysql-test/r/sp-analyze-stmt.result109
-rw-r--r--mysql-test/t/sp-analyze-stmt.test48
-rw-r--r--sql/sp_head.cc17
-rw-r--r--sql/sql_analyze_stmt.cc89
-rw-r--r--sql/sql_analyze_stmt.h34
-rw-r--r--sql/sql_explain.cc15
-rw-r--r--sql/sql_explain.h5
7 files changed, 316 insertions, 1 deletions
diff --git a/mysql-test/r/sp-analyze-stmt.result b/mysql-test/r/sp-analyze-stmt.result
new file mode 100644
index 00000000000..1709664385e
--- /dev/null
+++ b/mysql-test/r/sp-analyze-stmt.result
@@ -0,0 +1,109 @@
+drop table if exists t0,t1;
+drop function if exists f1;
+drop function if exists f2;
+create table t0(a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create function f1 (a int) returns int deterministic
+begin
+return a+1;
+end|
+create function f2 (a int) returns int deterministic
+begin
+return a+10;
+end|
+create function f3 (a int) returns int deterministic
+begin
+return f1(a) + f2(a);
+end|
+analyze format=json select a, f1(a),f2(a) from t0;
+ANALYZE
+{
+ "query_block": {
+ "select_id": 1,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "table": {
+ "table_name": "t0",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 10,
+ "r_rows": 10,
+ "r_total_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 100
+ }
+ },
+ "r_stored_routines": {
+ {
+ "qname": "test.f2",
+ "r_count": 10,
+ "r_total_time_ms": "REPLACED"
+ },
+ {
+ "qname": "test.f1",
+ "r_count": 10,
+ "r_total_time_ms": "REPLACED"
+ }
+ }
+}
+analyze format=json select a, f3(a) from t0;
+ANALYZE
+{
+ "query_block": {
+ "select_id": 1,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "table": {
+ "table_name": "t0",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 10,
+ "r_rows": 10,
+ "r_total_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 100
+ }
+ },
+ "r_stored_routines": {
+ {
+ "qname": "test.f3",
+ "r_count": 10,
+ "r_total_time_ms": "REPLACED"
+ }
+ }
+}
+analyze format=json select a, f1(a),f3(a) from t0;
+ANALYZE
+{
+ "query_block": {
+ "select_id": 1,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "table": {
+ "table_name": "t0",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 10,
+ "r_rows": 10,
+ "r_total_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 100
+ }
+ },
+ "r_stored_routines": {
+ {
+ "qname": "test.f3",
+ "r_count": 10,
+ "r_total_time_ms": "REPLACED"
+ },
+ {
+ "qname": "test.f1",
+ "r_count": 10,
+ "r_total_time_ms": "REPLACED"
+ }
+ }
+}
+drop table t0;
+drop function f1;
+drop function f2;
+drop function f3;
diff --git a/mysql-test/t/sp-analyze-stmt.test b/mysql-test/t/sp-analyze-stmt.test
new file mode 100644
index 00000000000..ff5d5970ff1
--- /dev/null
+++ b/mysql-test/t/sp-analyze-stmt.test
@@ -0,0 +1,48 @@
+#
+# MDEV-13330: Tests for the "ANALYZE command should collect SP execution time" feature
+#
+
+--disable_warnings
+drop table if exists t0,t1;
+drop function if exists f1;
+drop function if exists f2;
+--enable_warnings
+
+create table t0(a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+
+delimiter |;
+
+create function f1 (a int) returns int deterministic
+begin
+ return a+1;
+end|
+
+create function f2 (a int) returns int deterministic
+begin
+ return a+10;
+end|
+
+create function f3 (a int) returns int deterministic
+begin
+ return f1(a) + f2(a);
+end|
+
+delimiter ;|
+
+--source include/analyze-format.inc
+analyze format=json select a, f1(a),f2(a) from t0;
+
+--source include/analyze-format.inc
+analyze format=json select a, f3(a) from t0;
+
+--source include/analyze-format.inc
+analyze format=json select a, f1(a),f3(a) from t0;
+
+# TODO: tests for triggers.
+
+drop table t0;
+drop function f1;
+drop function f2;
+drop function f3;
+
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index a31060179e4..f7cdd602640 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -45,6 +45,9 @@
#include "sql_audit.h"
#include "debug_sync.h"
+#include "sql_select.h"
+#include "sql_explain.h"
+
/*
Sufficient max length of printed destinations and frame offsets (all uints).
*/
@@ -1145,6 +1148,13 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
if (check_stack_overrun(thd, 7 * STACK_MIN_SIZE, (uchar*)&old_packet))
DBUG_RETURN(TRUE);
+ if (thd->lex->explain && thd->lex->explain->sp_tracker)
+ {
+ // TODO: how about creating sp_tracker on demand? Most queries wont need
+ // it.
+ thd->lex->explain->sp_tracker->report_routine_start(&m_qname);
+ }
+
/* init per-instruction memroot */
init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
@@ -1452,6 +1462,13 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE);
}
+
+ //psergey-todo: put the second call here.
+ if (thd->lex->explain && thd->lex->explain->sp_tracker)
+ {
+ thd->lex->explain->sp_tracker->report_routine_end(&m_qname);
+ }
+
m_flags&= ~IS_INVOKED;
DBUG_PRINT("info",
("first free for 0x%lx --: 0x%lx->0x%lx, level: %lu, flags %x",
diff --git a/sql/sql_analyze_stmt.cc b/sql/sql_analyze_stmt.cc
index ac3797aae60..6c641005d96 100644
--- a/sql/sql_analyze_stmt.cc
+++ b/sql/sql_analyze_stmt.cc
@@ -80,3 +80,92 @@ void Filesort_tracker::print_json_members(Json_writer *writer)
}
}
+
+static
+uchar *get_sp_call_counter_name(void *arg,
+ size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ Stored_routine_tracker::SP_call_counter *counter=
+ (Stored_routine_tracker::SP_call_counter*)arg;
+
+ *length= counter->name.length;
+ return (uchar*)counter->name.str;
+}
+
+
+extern "C" void free_stored_routine_tracker(void *arg)
+{
+ Stored_routine_tracker::SP_call_counter *counter=
+ (Stored_routine_tracker::SP_call_counter*)arg;
+ delete counter;
+}
+
+
+Stored_routine_tracker::Stored_routine_tracker()
+//: time_tracker(false) //todo remove this
+{
+ //TODO: tracker should only be used when doing timing.
+ (void)my_hash_init(&name_to_counter,
+ system_charset_info, 10, 0, 0,
+ (my_hash_get_key) get_sp_call_counter_name,
+ free_stored_routine_tracker, HASH_UNIQUE);
+}
+
+
+Stored_routine_tracker::~Stored_routine_tracker()
+{
+ my_hash_free(&name_to_counter);
+}
+
+
+void Stored_routine_tracker::report_routine_start(const LEX_STRING *qname)
+{
+ SP_call_counter *cntr;
+
+ if (!(cntr= (SP_call_counter*) my_hash_search(&name_to_counter,
+ (const uchar*)qname->str,
+ qname->length)))
+ {
+ if ((cntr= new SP_call_counter))
+ {
+ cntr->name= *qname;
+ my_hash_insert(&name_to_counter, (uchar*)cntr);
+ }
+ }
+
+ if (cntr)
+ ANALYZE_START_TRACKING(&cntr->count);
+}
+
+void Stored_routine_tracker::report_routine_end(const LEX_STRING *qname)
+{
+ SP_call_counter *cntr;
+ if ((cntr= (SP_call_counter*) my_hash_search(&name_to_counter,
+ (const uchar*)qname->str,
+ qname->length)))
+ {
+ ANALYZE_STOP_TRACKING(&cntr->count);
+ }
+}
+
+void Stored_routine_tracker::print_json_members(Json_writer *writer)
+{
+ if (name_to_counter.records)
+ {
+ writer->add_member("r_stored_routines").start_object();
+ uint i;
+ for (i= 0; i < name_to_counter.records; ++i)
+ {
+ SP_call_counter *cntr=
+ (SP_call_counter*)my_hash_element(&name_to_counter, i);
+ writer->start_object();
+ writer->add_member("qname").add_str(cntr->name.str);
+ writer->add_member("r_count").add_ll(cntr->count.get_loops());
+ writer->add_member("r_total_time_ms").add_double(cntr->count.get_time_ms());
+ writer->end_object();
+ }
+ writer->end_object();
+ }
+}
+
diff --git a/sql/sql_analyze_stmt.h b/sql/sql_analyze_stmt.h
index 27fd7fb6d6a..e78379d15a6 100644
--- a/sql/sql_analyze_stmt.h
+++ b/sql/sql_analyze_stmt.h
@@ -284,3 +284,37 @@ private:
ulonglong sort_buffer_size;
};
+
+/*
+ Tracks number of executions and total time spent in each routine invoked by
+ the statement.
+
+ Tracking happens only for routine invocations made directly by this
+ statement.
+ For example, if the statement invokes function F1() which invokes F2(), we will not
+ report any calls to F2. Time spent in F2() will be billed to F1().
+*/
+class Stored_routine_tracker : public Sql_alloc
+{
+ HASH name_to_counter;
+
+public:
+ class SP_call_counter
+ {
+ public:
+ SP_call_counter() : count(true) {}
+ LEX_STRING name;
+ Time_and_counter_tracker count;
+ };
+
+ Stored_routine_tracker(); //: time_tracker(do_timing) {}
+ ~Stored_routine_tracker();
+
+ /* Functions to report SP invocations */
+
+ void report_routine_start(const LEX_STRING *qname);
+ void report_routine_end(const LEX_STRING *qname);
+
+ /* Functions to get the statistics */
+ void print_json_members(Json_writer *writer);
+};
diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc
index 9b474fd5dee..59c70849709 100644
--- a/sql/sql_explain.cc
+++ b/sql/sql_explain.cc
@@ -33,7 +33,8 @@ static void write_item(Json_writer *writer, Item *item);
static void append_item_to_str(String *out, Item *item);
Explain_query::Explain_query(THD *thd_arg, MEM_ROOT *root) :
- mem_root(root), upd_del_plan(NULL), insert_plan(NULL),
+ mem_root(root), sp_tracker(NULL),
+ upd_del_plan(NULL), insert_plan(NULL),
unions(root), selects(root), thd(thd_arg), apc_enabled(false),
operations(0)
{
@@ -59,6 +60,8 @@ Explain_query::~Explain_query()
delete upd_del_plan;
delete insert_plan;
+
+ delete sp_tracker;
uint i;
for (i= 0 ; i < unions.elements(); i++)
delete unions.at(i);
@@ -144,6 +147,13 @@ void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg)
void Explain_query::query_plan_ready()
{
+ // Q: the below doesn't look stellar module-wise.
+ if (current_thd->lex->analyze_stmt)
+ {
+ // Tracking is only done when running ANALYZE.
+ sp_tracker= new Stored_routine_tracker();
+ }
+
if (!apc_enabled)
thd->apc_target.enable();
apc_enabled= true;
@@ -224,6 +234,9 @@ void Explain_query::print_explain_json(select_result_sink *output,
node->print_explain_json(this, &writer, is_analyze);
}
+ if (sp_tracker)
+ sp_tracker->print_json_members(&writer);
+
writer.end_object();
CHARSET_INFO *cs= system_charset_info;
diff --git a/sql/sql_explain.h b/sql/sql_explain.h
index bfddf40ff33..71101b357ff 100644
--- a/sql/sql_explain.h
+++ b/sql/sql_explain.h
@@ -468,6 +468,11 @@ public:
MEM_ROOT *mem_root;
Explain_update *get_upd_del_plan() { return upd_del_plan; }
+
+ /*
+ Tracks invocations of stored routines. NULL for non-ANALYZE statements
+ */
+ Stored_routine_tracker* sp_tracker;
private:
/* Explain_delete inherits from Explain_update */
Explain_update *upd_del_plan;