1 /* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /**
24 @file storage/perfschema/pfs_data_lock.cc
25 The performance schema implementation for data locks.
26 */
27
28 #include "storage/perfschema/pfs_data_lock.h"
29
30 #include <stddef.h>
31
32 #include "my_dbug.h"
33
34 /* clang-format off */
35 /**
36 @page PAGE_PFS_DATA_LOCKS Performance schema data locks
37
38 @section SERVER_ENGINE_INTERFACE Server / Storage engine interface
39
40 @subsection SE_INTERFACE_REGISTRATION Registration
41
42 @startuml
43
44 title Registration
45
46 participant server as "MySQL server"
47 participant pfs as "Performance schema"
48 participant se as "Storage Engine"
49 participant se_inspector as "Storage Engine\nData Lock Inspector"
50
51 == plugin init ==
52 server -> se : plugin_init()
53 se -> pfs : register_data_lock()
54
55 == SELECT * FROM performance_schema.data_locks ==
56 server -> pfs : table_data_locks::rnd_next()
57 pfs -> se_inspector : (multiple calls)
58
59 == plugin deinit ==
60 server -> se : plugin_deinit()
61 se -> pfs : unregister_data_lock()
62
63 @enduml
64
65 To expose DATA_LOCKS to the performance schema,
66 a storage engine needs to:
67 - implement a sub class of #PSI_engine_data_lock_inspector
68 - register it with the performance schema on init
69 - unregister it with the performance schema on deinit
70
71 While the storage engine is in use (between init and deinit),
72 the performance schema keeps a reference to the data lock inspector given,
73 and use it to inspect the storage engine data locks.
74
75 @subsection SE_INTERFACE_SCAN_1 Iteration for each storage engine
76
77 @startuml
78
79 title Iteration for each storage engine
80
81 participant server as "MySQL server"
82 participant pfs as "Performance schema\nTable data_locks"
83 participant pfs_container as "Performance schema\nData Lock container"
84 participant se_inspector as "Storage Engine\nData Lock Inspector"
85 participant se_iterator as "Storage Engine\nData Lock Iterator"
86
87 == SELECT init ==
88 server -> pfs : rnd_init()
89 activate pfs_container
90 pfs -> pfs_container : create()
91
92 == For each storage engine ==
93 pfs -> se_inspector : create_iterator()
94 activate se_iterator
95 se_inspector -> se_iterator : create()
96
97 pfs -> se_iterator : (multiple calls)
98
99 pfs -> se_iterator : destroy()
100 deactivate se_iterator
101
102 == SELECT end ==
103 server -> pfs : rnd_end()
104 pfs -> pfs_container : destroy()
105 deactivate pfs_container
106
107 @enduml
108
109 When the server performs a SELECT * from performance_schema.data_locks,
110 the performance schema creates a #PSI_server_data_lock_container for
111 the duration of the table scan.
112
113 Then, the scan loops for each storage engine capable of exposing data locks
114 (that is, engines that registered a data lock inspector).
115
116 For each engine, the inspector is called to create an iterator,
117 dedicated for this SELECT scan.
118
119 @subsection SE_INTERFACE_SCAN_2 Iteration inside a storage engine
120
121 @startuml
122
123 title Iteration inside a storage engine
124
125 participant server as "MySQL server"
126 participant pfs as "Performance schema\nTable data_locks"
127 participant pfs_container as "Performance schema\nData Lock container"
128 participant se_iterator as "Storage Engine\nData Lock Iterator"
129
130 loop until the storage engine iterator is done
131
132 == SELECT scan ==
133
134 server -> pfs : rnd_next()
135
136 == First scan, fetch N rows at once from the storage engine ==
137
138 pfs -> se_iterator : scan()
139 se_iterator -> pfs_container : add_row() // 1
140 se_iterator -> pfs_container : add_row() // 2
141 se_iterator -> pfs_container : ...
142 se_iterator -> pfs_container : add_row() // N
143 pfs -> pfs_container : get_row(1)
144 pfs -> server : result set row 1
145
146 == Subsequent scans, return the rows collected ==
147
148 server -> pfs : rnd_next()
149 pfs -> pfs_container : get_row(2)
150 pfs -> server : result set row 2
151
152 server -> pfs : rnd_next()
153 pfs -> pfs_container : get_row(...)
154 pfs -> server : result set row ...
155
156 server -> pfs : rnd_next()
157 pfs -> pfs_container : get_row(N)
158 pfs -> server : result set row N
159
160 end
161
162 @enduml
163
164 When table_data_locks::rnd_next() is first called,
165 the performance schema calls the storage engine iterator,
166 which adds N rows in the data container.
167
168 Upon subsequent calls to table_data_locks::rnd_next(),
169 data present in the container is returned.
170 This process loops until the storage engine iterator finally reports
171 that it reached the end of the scan.
172
173 Note that the storage engine iterator has freedom to implement:
174 - either a full table scan, returning all rows in a single call,
175 - or a restartable scan, returning only a few rows in each call.
176
177 The major benefit of this interface is that the engine iterator
178 can stop and restart a scan at natural boundaries within the
179 storage engine (say, return all the locks for one transaction per call),
180 which simplifies a lot the storage engine implementation.
181 */
182 /* clang-format on */
183
PFS_data_cache()184 PFS_data_cache::PFS_data_cache() {}
185
~PFS_data_cache()186 PFS_data_cache::~PFS_data_cache() {}
187
cache_data(const char * ptr,size_t length)188 const char *PFS_data_cache::cache_data(const char *ptr, size_t length) {
189 /*
190 std::string is just a sequence of bytes,
191 which actually can contain a 0 byte ...
192 Never use strlen() on the binary data.
193 */
194 std::string key(ptr, length);
195 std::pair<set_type::iterator, bool> ret;
196
197 ret = m_set.insert(key);
198 return (*ret.first).data();
199 }
200
clear()201 void PFS_data_cache::clear() { m_set.clear(); }
202
PFS_data_lock_container()203 PFS_data_lock_container::PFS_data_lock_container()
204 : m_logical_row_index(0), m_filter(nullptr) {}
205
~PFS_data_lock_container()206 PFS_data_lock_container::~PFS_data_lock_container() {}
207
cache_string(const char * string)208 const char *PFS_data_lock_container::cache_string(const char *string) {
209 return m_cache.cache_data(string, strlen(string));
210 }
211
cache_data(const char * ptr,size_t length)212 const char *PFS_data_lock_container::cache_data(const char *ptr,
213 size_t length) {
214 return m_cache.cache_data(ptr, length);
215 }
216
accept_engine(const char * engine,size_t engine_length)217 bool PFS_data_lock_container::accept_engine(const char *engine,
218 size_t engine_length) {
219 if (m_filter != nullptr) {
220 return m_filter->match_engine(engine, engine_length);
221 }
222 return true;
223 }
224
accept_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)225 bool PFS_data_lock_container::accept_lock_id(const char *engine_lock_id,
226 size_t engine_lock_id_length) {
227 if (m_filter != nullptr) {
228 return m_filter->match_lock_id(engine_lock_id, engine_lock_id_length);
229 }
230 return true;
231 }
232
accept_transaction_id(ulonglong transaction_id)233 bool PFS_data_lock_container::accept_transaction_id(ulonglong transaction_id) {
234 if (m_filter != nullptr) {
235 return m_filter->match_transaction_id(transaction_id);
236 }
237 return true;
238 }
239
accept_thread_id_event_id(ulonglong thread_id,ulonglong event_id)240 bool PFS_data_lock_container::accept_thread_id_event_id(ulonglong thread_id,
241 ulonglong event_id) {
242 if (m_filter != nullptr) {
243 return m_filter->match_thread_id_event_id(thread_id, event_id);
244 }
245 return true;
246 }
247
accept_object(const char * table_schema,size_t table_schema_length,const char * table_name,size_t table_name_length,const char * partition_name,size_t partition_name_length,const char * sub_partition_name,size_t sub_partition_name_length)248 bool PFS_data_lock_container::accept_object(
249 const char *table_schema, size_t table_schema_length,
250 const char *table_name, size_t table_name_length,
251 const char *partition_name, size_t partition_name_length,
252 const char *sub_partition_name, size_t sub_partition_name_length) {
253 if (m_filter != nullptr) {
254 return m_filter->match_object(table_schema, table_schema_length, table_name,
255 table_name_length, partition_name,
256 partition_name_length, sub_partition_name,
257 sub_partition_name_length);
258 }
259 return true;
260 }
261
add_lock_row(const char * engine,size_t engine_length MY_ATTRIBUTE ((unused)),const char * engine_lock_id,size_t engine_lock_id_length,ulonglong transaction_id,ulonglong thread_id,ulonglong event_id,const char * table_schema,size_t table_schema_length,const char * table_name,size_t table_name_length,const char * partition_name,size_t partition_name_length,const char * sub_partition_name,size_t sub_partition_name_length,const char * index_name,size_t index_name_length,const void * identity,const char * lock_mode,const char * lock_type,const char * lock_status,const char * lock_data)262 void PFS_data_lock_container::add_lock_row(
263 const char *engine, size_t engine_length MY_ATTRIBUTE((unused)),
264 const char *engine_lock_id, size_t engine_lock_id_length,
265 ulonglong transaction_id, ulonglong thread_id, ulonglong event_id,
266 const char *table_schema, size_t table_schema_length,
267 const char *table_name, size_t table_name_length,
268 const char *partition_name, size_t partition_name_length,
269 const char *sub_partition_name, size_t sub_partition_name_length,
270 const char *index_name, size_t index_name_length, const void *identity,
271 const char *lock_mode, const char *lock_type, const char *lock_status,
272 const char *lock_data) {
273 row_data_lock row;
274
275 row.m_engine = engine;
276
277 if (engine_lock_id != nullptr) {
278 size_t len = engine_lock_id_length;
279 if (len > sizeof(row.m_hidden_pk.m_engine_lock_id)) {
280 DBUG_ASSERT(false);
281 len = sizeof(row.m_hidden_pk.m_engine_lock_id);
282 }
283 if (len > 0) {
284 memcpy(row.m_hidden_pk.m_engine_lock_id, engine_lock_id, len);
285 }
286 row.m_hidden_pk.m_engine_lock_id_length = len;
287 } else {
288 row.m_hidden_pk.m_engine_lock_id_length = 0;
289 }
290
291 row.m_transaction_id = transaction_id;
292 row.m_thread_id = thread_id;
293 row.m_event_id = event_id;
294
295 row.m_index_row.m_object_row.m_object_type = OBJECT_TYPE_TABLE;
296
297 if (table_schema_length > 0) {
298 memcpy(row.m_index_row.m_object_row.m_schema_name, table_schema,
299 table_schema_length);
300 }
301 row.m_index_row.m_object_row.m_schema_name_length = table_schema_length;
302
303 if (table_name_length > 0) {
304 memcpy(row.m_index_row.m_object_row.m_object_name, table_name,
305 table_name_length);
306 }
307 row.m_index_row.m_object_row.m_object_name_length = table_name_length;
308
309 row.m_partition_name = partition_name;
310 row.m_partition_name_length = partition_name_length;
311
312 row.m_sub_partition_name = sub_partition_name;
313 row.m_sub_partition_name_length = sub_partition_name_length;
314
315 if (index_name_length > 0) {
316 memcpy(row.m_index_row.m_index_name, index_name, index_name_length);
317 }
318 row.m_index_row.m_index_name_length = index_name_length;
319
320 row.m_identity = identity;
321 row.m_lock_mode = lock_mode;
322 row.m_lock_type = lock_type;
323 row.m_lock_status = lock_status;
324 row.m_lock_data = lock_data;
325
326 m_rows.push_back(row);
327 }
328
clear()329 void PFS_data_lock_container::clear() {
330 m_logical_row_index = 0;
331 m_rows.clear();
332 m_cache.clear();
333 }
334
shrink()335 void PFS_data_lock_container::shrink() {
336 /* Keep rows numbering. */
337 m_logical_row_index += m_rows.size();
338 /* Discard existing data. */
339 m_rows.clear();
340 m_cache.clear();
341 }
342
get_row(size_t index)343 row_data_lock *PFS_data_lock_container::get_row(size_t index) {
344 if (index < m_logical_row_index) {
345 /*
346 This row existed, before a call to ::shrink().
347 The caller should not ask for it again.
348 */
349 DBUG_ASSERT(false);
350 return nullptr;
351 }
352
353 size_t physical_index = index - m_logical_row_index;
354
355 if (physical_index < m_rows.size()) {
356 return &m_rows[physical_index];
357 }
358
359 return nullptr;
360 }
361
PFS_data_lock_wait_container()362 PFS_data_lock_wait_container::PFS_data_lock_wait_container()
363 : m_logical_row_index(0), m_filter(nullptr) {}
364
~PFS_data_lock_wait_container()365 PFS_data_lock_wait_container::~PFS_data_lock_wait_container() {}
366
cache_string(const char * string)367 const char *PFS_data_lock_wait_container::cache_string(const char *string) {
368 return m_cache.cache_data(string, strlen(string));
369 }
370
cache_data(const char * ptr,size_t length)371 const char *PFS_data_lock_wait_container::cache_data(const char *ptr,
372 size_t length) {
373 return m_cache.cache_data(ptr, length);
374 }
375
accept_engine(const char * engine,size_t engine_length)376 bool PFS_data_lock_wait_container::accept_engine(const char *engine,
377 size_t engine_length) {
378 if (m_filter != nullptr) {
379 return m_filter->match_engine(engine, engine_length);
380 }
381 return true;
382 }
383
accept_requesting_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)384 bool PFS_data_lock_wait_container::accept_requesting_lock_id(
385 const char *engine_lock_id, size_t engine_lock_id_length) {
386 if (m_filter != nullptr) {
387 return m_filter->match_requesting_lock_id(engine_lock_id,
388 engine_lock_id_length);
389 }
390 return true;
391 }
392
accept_blocking_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)393 bool PFS_data_lock_wait_container::accept_blocking_lock_id(
394 const char *engine_lock_id, size_t engine_lock_id_length) {
395 if (m_filter != nullptr) {
396 return m_filter->match_blocking_lock_id(engine_lock_id,
397 engine_lock_id_length);
398 }
399 return true;
400 }
401
accept_requesting_transaction_id(ulonglong transaction_id)402 bool PFS_data_lock_wait_container::accept_requesting_transaction_id(
403 ulonglong transaction_id) {
404 if (m_filter != nullptr) {
405 return m_filter->match_requesting_transaction_id(transaction_id);
406 }
407 return true;
408 }
409
accept_blocking_transaction_id(ulonglong transaction_id)410 bool PFS_data_lock_wait_container::accept_blocking_transaction_id(
411 ulonglong transaction_id) {
412 if (m_filter != nullptr) {
413 return m_filter->match_blocking_transaction_id(transaction_id);
414 }
415 return true;
416 }
417
accept_requesting_thread_id_event_id(ulonglong thread_id,ulonglong event_id)418 bool PFS_data_lock_wait_container::accept_requesting_thread_id_event_id(
419 ulonglong thread_id, ulonglong event_id) {
420 if (m_filter != nullptr) {
421 return m_filter->match_requesting_thread_id_event_id(thread_id, event_id);
422 }
423 return true;
424 }
425
accept_blocking_thread_id_event_id(ulonglong thread_id,ulonglong event_id)426 bool PFS_data_lock_wait_container::accept_blocking_thread_id_event_id(
427 ulonglong thread_id, ulonglong event_id) {
428 if (m_filter != nullptr) {
429 return m_filter->match_blocking_thread_id_event_id(thread_id, event_id);
430 }
431 return true;
432 }
433
add_lock_wait_row(const char * engine,size_t engine_length MY_ATTRIBUTE ((unused)),const char * requesting_engine_lock_id,size_t requesting_engine_lock_id_length,ulonglong requesting_transaction_id,ulonglong requesting_thread_id,ulonglong requesting_event_id,const void * requesting_identity,const char * blocking_engine_lock_id,size_t blocking_engine_lock_id_length,ulonglong blocking_transaction_id,ulonglong blocking_thread_id,ulonglong blocking_event_id,const void * blocking_identity)434 void PFS_data_lock_wait_container::add_lock_wait_row(
435 const char *engine, size_t engine_length MY_ATTRIBUTE((unused)),
436 const char *requesting_engine_lock_id,
437 size_t requesting_engine_lock_id_length,
438 ulonglong requesting_transaction_id, ulonglong requesting_thread_id,
439 ulonglong requesting_event_id, const void *requesting_identity,
440 const char *blocking_engine_lock_id, size_t blocking_engine_lock_id_length,
441 ulonglong blocking_transaction_id, ulonglong blocking_thread_id,
442 ulonglong blocking_event_id, const void *blocking_identity) {
443 row_data_lock_wait row;
444
445 row.m_engine = engine;
446
447 if (requesting_engine_lock_id != nullptr) {
448 size_t len = requesting_engine_lock_id_length;
449 if (len > sizeof(row.m_hidden_pk.m_requesting_engine_lock_id)) {
450 DBUG_ASSERT(false);
451 len = sizeof(row.m_hidden_pk.m_requesting_engine_lock_id);
452 }
453 if (len > 0) {
454 memcpy(row.m_hidden_pk.m_requesting_engine_lock_id,
455 requesting_engine_lock_id, len);
456 }
457 row.m_hidden_pk.m_requesting_engine_lock_id_length = len;
458 } else {
459 row.m_hidden_pk.m_requesting_engine_lock_id_length = 0;
460 }
461
462 row.m_requesting_transaction_id = requesting_transaction_id;
463 row.m_requesting_thread_id = requesting_thread_id;
464 row.m_requesting_event_id = requesting_event_id;
465 row.m_requesting_identity = requesting_identity;
466
467 if (blocking_engine_lock_id != nullptr) {
468 size_t len = blocking_engine_lock_id_length;
469 if (len > sizeof(row.m_hidden_pk.m_blocking_engine_lock_id)) {
470 DBUG_ASSERT(false);
471 len = sizeof(row.m_hidden_pk.m_blocking_engine_lock_id);
472 }
473 if (len > 0) {
474 memcpy(row.m_hidden_pk.m_blocking_engine_lock_id, blocking_engine_lock_id,
475 len);
476 }
477 row.m_hidden_pk.m_blocking_engine_lock_id_length = len;
478 } else {
479 row.m_hidden_pk.m_blocking_engine_lock_id_length = 0;
480 }
481
482 row.m_blocking_transaction_id = blocking_transaction_id;
483 row.m_blocking_thread_id = blocking_thread_id;
484 row.m_blocking_event_id = blocking_event_id;
485 row.m_blocking_identity = blocking_identity;
486
487 m_rows.push_back(row);
488 }
489
clear()490 void PFS_data_lock_wait_container::clear() {
491 m_logical_row_index = 0;
492 m_rows.clear();
493 m_cache.clear();
494 }
495
shrink()496 void PFS_data_lock_wait_container::shrink() {
497 /* Keep rows numbering. */
498 m_logical_row_index += m_rows.size();
499 /* Discard existing data. */
500 m_rows.clear();
501 m_cache.clear();
502 }
503
get_row(size_t index)504 row_data_lock_wait *PFS_data_lock_wait_container::get_row(size_t index) {
505 if (index < m_logical_row_index) {
506 /*
507 This row existed, before a call to ::shrink().
508 The caller should not ask for it again.
509 */
510 DBUG_ASSERT(false);
511 return nullptr;
512 }
513
514 size_t physical_index = index - m_logical_row_index;
515
516 if (physical_index < m_rows.size()) {
517 return &m_rows[physical_index];
518 }
519
520 return nullptr;
521 }
522