WL#6226: Protocol tracing - client side
Affects: Server-5.7
—
Status: Complete
Rationale ========= This opens a possibility to collect data about performance of client-server connection (on client side). The CTA plugin for libmysql will use this protocol-tracing infrastructure. Description =========== Define hooks inside client library code which allow tracing of protocol events such as: - sending packets to server, - receiving various kinds of server replies (simple ERR/OK, resultset metadata, resultset rows etc), - various stages of authentication handshake. Normally these hooks will do nothing (and should introduce minimal overhead). But there will be a possibility to load a trace plugin, which would be invoked by the hooks and which can process the events in some way. Notes ===== - Pushed to mysql-trunk on 2013-07-24. User Documentation ================== http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-2.html http://dev.mysql.com/doc/refman/5.7/en/writing-protocol-trace-plugins.html
A new type of client-side plugin, MYSQL_CLIENT_TRACE_PLUGIN will be defined.
Such trace plugin, if loaded into libmysql, will receive notifications about
various events related to MySQL protocol exchange with the server. It is up to
the plugin to decide what to do with this information.
Only one trace plugin can be loaded at a time. Attempts to load another plugin
of type MYSQL_CLIENT_TRACE_PLUGIN when one is already loaded will trigger an
error.
After plugin is loaded, all new connections will be traced using that plugin.
Connections which were active at the time of loading the plugin will not be
traced.
Note: Only new connections can be traced and tracing always starts in
CONNECTING stage. An on-going connection which was not traced from the beginning
can not be traced later.
Trace plugin methods
====================
A trace plugin will provide the following methods:
tracing_start
-------------
Called when tracing with this plugin is enabled on a connection.
Input:
- pointer to the plugin instance (self),
- connection handle,
Output:
- pointer to plugin-specific, per-connection data (if any).
A trace plugin might want to maintain a per-connection information. It can
return a pointer to memory area holding such information. It will be stored in a
connection handle and passed to other plugin methods.
tracing_stop
------------
Called when tracing with this plugin on a connection has stopped.
Input:
- pointer to the plugin instance (self),
- connection handle,
- this plugin's per-connection data (if any),
Output: none.
If a plugin allocated any per-connection resources, it should de-allocate them
here.
trace_event
-----------
Called when a trace event occurs.
Input:
- pointer to the plugin instance (self),
- trace event
- current protocol stage
- connection handle
- plugin's per-connection data (if any),
- optional trace event parameters.
Output:
Indication whether tracing of the connection should be stopped here or
continued.
It is up to the plugin to decide what to do in this method. However, it can not
send queries or other commands to the server because trace_event method can be
called inside on-going conversation between client and server. The only
exception is the AUTHENTICATED trace event (see below). When this event is
passed to trace_event() method, the state of the client is such that it can send
new queries and commands to the server. Generating of trace events will be
suppressed for queries and commands issued inside trace_event() method.
Trace events
============
A trace plugin will be notified of the following events:
---------------------- -----------------------------------------------------
Connection events
---------------------- -----------------------------------------------------
CONNECTING Client is connecting to the server.
CONNECTED Physical connection has been established.
DISCONNECTED Connection with server was terminated.
---------------------- -----------------------------------------------------
SSL events
---------------------- -----------------------------------------------------
SEND_SSL_REQUEST Client is sending SSL connection request.
SSL_CONNECT Client is initiating SSL handshake.
SSL_CONNECTED SSL connection has been established.
---------------------- -----------------------------------------------------
Authentication events
---------------------- -----------------------------------------------------
INIT_PACKET_RECEIVED Client received initial handshake packet.
AUTH_PLUGIN Client selects an authentication plugin to be used
in the following authentication exchange.
SEND_AUTH_RESPONSE Client sends response to the authentication
challenge.
SEND_AUTH_DATA Client sends extra authentication data packet.
AUTHENTICATED Server has accepted connection.
---------------------- -----------------------------------------------------
Command phase events
---------------------- -----------------------------------------------------
SEND_COMMAND Cleint is sending a command to the server.
SEND_FILE Client is sending local file contents to the server.
---------------------- -----------------------------------------------------
General events
---------------------- -----------------------------------------------------
READ_PACKET Client starts waiting for a packet from server.
PACKET_RECEIVED A packet from server has been received.
PACKET_SENT After successful sending of a packet to the server.
ERROR Client detected an error.
---------------------- -----------------------------------------------------
Note: The following commands used for replication are not supported:
COM_BINLOG_DUMP, COM_BINLOG_DUMP_GTID, COM_TABLE_DUMP. When such command is
issued by client, tracing of the connection will stop after sending DISCONNECTED
event to the plugin.
Trace event data
----------------
For same events additional information will be passed to event handler:
- Packet to be sent and its length for: SEND_SSL_REQUEST,
SEND_AUTH_RESPONSE, SEND_AUTH_DATA, SEND_FILE.
- For SEND_COMMAND, the command code and header and arguments part of the
command packet with their lengths (as accepted by net_write_command).
- Packet received from server and its length for: PACKET_RECEIVED and
CHALLENGE_RECEIVED.
- Authentication plugin name for AUTH_PLUGIN.
MySQL protocol stages
=====================
A client following the MySQL protocol goes through several stages of it. Each
stage determines what packets can be expected from the server or can be send by
the client.
Upon receiving each trace event, trace plugin will be notified of the current
protocol stage so that it can correctly interpret the event.
These are the possible protocol stages and the transitions between them.
.. digraph:: protocol_stages
CONNECTING -> WAIT_FOR_INIT_PACKET;
CONNECTING -> DISCONNECTED [ label = "failed connection" ];
WAIT_FOR_INIT_PACKET -> AUTHENTICATE;
WAIT_FOR_INIT_PACKET -> SSL_NEGOTIATION -> AUTHENTICATE;
AUTHENTICATE -> READY_FOR_COMMAND [ label = "accepted" ];
AUTHENTICATE -> DISCONNECTED [ label = "rejected" ];
READY_FOR_COMMAND -> DISCONNECTED [ label = "COM_QUIT" ];
READY_FOR_COMMAND -> AUTHENTICATE [ label="after change user" ];
READY_FOR_COMMAND -> WAIT_FOR_PACKET -> READY_FOR_COMAND;
READY_FOR_COMMAND -> WAIT_FOR_RESULT
READY_FOR_COMMAND -> WAIT_FOR_PS_DESCRIPTION
[ label="after prepare command" ];
READY_FOR_COMMAND -> WAIT_FOR_FIELD_DEF
[ label="COM_FIELD_LIST" ];
WAIT_FOR_RESULT -> READY_FOR_COMMAND [ label="simple reply" ];
WAIT_FOR_RESULT -> WAIT_FOR_FIELD_DEF;
WAIT_FOR_RESULT -> FILE_REQUEST;
WAIT_FOR_FIELD_DEF -> WAIT_FOR_ROW [ label="in a resultset" ];
WAIT_FOR_FIELD_DEF -> READY_FOR_COMMAND
[ label="after describe table or prepare command" ];
WAIT_FOR_ROW -> READY_FOR_COMMAND;
WAIT_FOR_ROW -> WAIT_FOR_RESULT [ label="multi-resultset" ];
WAIT_FOR_PS_DESCRIPTION -> WAIT_FOR_PS_PARAMETER;
WAIT_FOR_PS_DESCRIPTION -> READY_FOR_COMMAND
[ label="no params and result" ];
WAIT_FOR_PS_DESCRIPTION -> WAIT_FOR_FIELD_DEF [ label="no params" ];
WAIT_FOR_PS_PARAMETER -> WAIT_FOR_FIELD_DEF;
WAIT_FOR_PS_PARAMETER -> READY_FOR_COMMAND [ label="no result" ];
FILE_REQUEST -> WAIT_FOR_RESULT [label="when whole file sent"];
Per-connection data for protocol tracing
========================================
The protocol tracing subsystem will store the following data in each connection
handle:
------------------- -------------------------------------------------------
plugin pointer to the plugin tracing this connection
------------------- -------------------------------------------------------
trace_plugin_data pointer to the plugin specific data as returned by
plugin's tracing_start method
------------------- -------------------------------------------------------
stage current stage of the protocol
------------------- -------------------------------------------------------
struct st_mysql_trace_info
{
struct st_mysql_client_plugin_TRACE *plugin;
void *trace_plugin_data;
enum mysql_trace_stage stage;
};
A pointer to st_mysql_trace_info struture will be stored inside
st_mysql_extension structure pointed by MYSQL::extension member:
struct st_mysql_extension {
struct st_mysql_trace_info *trace_data;
}
The MYSQL::extension will be allocated only if used. Macro GET_EXTENSION(M) will
return a pointer to the extension member of MYSQL handle M, allocating it if not
done already. The memory pointed by MYSQL::extension will be freed inside
mysql_close() function.
Trace plugin API
================
typedef
void* (tracing_start_callback)(struct st_mysql_client_plugin_TRACE *self,
struct st_mysql *connection_handle,
enum mysql_trace_stage stage);
typedef
void (tracing_stop_callback)(struct st_mysql_client_plugin_TRACE *self,
struct st_mysql *connection_handle,
void *plugin_data, short int shutdown);
typedef
int (trace_event_handler)(struct st_mysql_client_plugin_TRACE *self,
void *data,
struct st_mysql *connection_handle,
enum mysql_trace_stage stage,
enum mysql_trace_event event,
struct st_trace_event_args args);
struct st_mysql_client_plugin_TRACE
{
MYSQL_CLIENT_PLUGIN_HEADER
tracing_start_callback *tracing_start;
tracing_stop_callback *tracing_stop;
trace_event_handler *trace_event;
};
enum mysql_trace_stage contains PROTOCOL_STAGE_XXX constant for each
protocol stage specified in HLS.
enum mysql_trace_event contains TRACE_EVENT_XXX constant for each trace
event specified in HLS.
Structure st_trace_event_args contains members to store all possible trace event
arguments:
struct st_trace_event_args
{
const char *plugin_name;
int cmd;
const unsigned char *hdr;
size_t hdr_len;
const unsigned char *pkt;
size_t pkt_len;
};
Various events store their arguments in the structure as follows. Unused members
are set to 0/NULL.
AUTH_PLUGIN
------------- ----------------------------------
plugin_name the name of the plugin
------------- ----------------------------------
SEND_COMMAND
------------- ----------------------------------
cmd the command code
hdr pointer to command packet header
hdr_len length of the header
pkt pointer to command arguments
pkt_len length of arguments
------------- ----------------------------------
Other SEND_* and *_RECEIVED events
------------- ----------------------------------
pkt the data sent or received
pkt_len length of the data
------------- ----------------------------------
PACKET_SENT
------------- ----------------------------------
pkt_len number of bytes sent
------------- ----------------------------------
Global trace plugin pointer
===========================
A global pointer to the loaded trace plugin instance will be defined:
extern "C" struct st_mysql_client_plugin_TRACE *trace_plugin;
Initially this pointer will be NULL. It will be set inside add_plugin() function
in cleint_plugin.c if it is discovered that the plugin being added is a trace
plugin.
If a trace plugin is loaded and trace_plugin is not NULL, an attempt to load
another trace plugin will lead to an error.
When a trace plugin is loaded, trace events in on-going connections will not be
reported to it. Only new connections starting in CONNECTING phase will be traced
with the plugin. Connections which are traced and for which trace events are
reported to the trace plugin are distinguished by the fact that for them
MYSQL::extension->trace_data is not NULL.
Handling of trace events
========================
In places where trace events happen, trace hook will be inserted. It will
be a call of MYSQL_TRACE macro like this:
MYSQL_TRACE(PACKET_RECEIVED, mysql, (len, net->read_pos));
where PACKET_RECEIVED is the event which happens, followed by connection
handle and optional event parameters.
The macro will expand to code for processing trace event. However, processing
will be skipped if global trace_plugin pointer is NULL (no trace plugin loaded
yet) or this connection is not traced (MYSQL::extension->trace_data is NULL).
Tracing connection is initialized and connection's trace_data allocated when
CONNECTING stage is set with MYSQL_TRACE_STAGE() macro (see below).
The logic for processing trace event in MYSQL_TRACE(Event, M, Args) macro will
be as follows.
Note: not all of the logic needs to be in the macro expansion. For
efficiency, mostly the initial test determining whether further processing will
be done should be inlined as macro expansion. The actual event
processing logic can be implemented as inline functions for example.
--------------------------------------------------------------------
if trace_plugin is NULL or M->extension->trace_data is NULL
then do nothing and stop;
let trace_data= M->extension->trace_data;
if defined, call trace_event(...) method of trace_plugin passing to it:
- current event: Event,
- connection handle: M,
- current protocol stage: trace_data->stage,
- plugin's per-connection data: trace_data->trace_plugin_data,
- event arguments: Args.
if current stage or Event is DISCONNECTED
or trace_event() method returned non-zero value
then
call tracing_stop(...) method of trace_plugin, if defined.
free M->extension->trace_data and set it to NULL (this disables
further tracing in this connection);
fi
--------------------------------------------------------------------
Protocol Stage Changes
======================
Hooks will be placed in code in places where new protocol stage is entered.
These will be calls of MYSQL_TRACE_STAGE() macro like this:
MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT);
The macro will store the new protocol stage in mysql->extension->tace_data.stage
and this will be used when next trace event is reported to the trace plugin.
If the stage is CONNECTING, tracing in the connection will be initialized
(unless trace_plugin is NULL):
1. allocate trace_data structure and store pointer to it in
mysql->extension,
2. set trace_data.plugin to trace_plugin, trace_plugin.stage to CONNECTING,
3. call plugins trace_start() method, if defined.
4. if trace_start() returned a pointer, store it in
trace_data.trace_plugin_data, otherwise it is set to NULL.
Thread safety
=============
Tracing infrastructure does not enforce any synchronization between different
application threads. This should be safe, assuming that only one application
thread can use given MYSQL connection instance. Under that assumption the only
shared resource is the global trace_plugin pointer. Even without
synchronization, the following code is thread safe:
if trace_plugin not NULL then
use trace_plugin ...
fi
This is because tace_plugin can be set from NULL to something not-NULL in a
parallel thread, but once it is not-NULL, its value will never change.
Important Note
--------------
Trace plugin methods must be thread-safe, as they can be called from different
threads without any synchronization.
Implementation
==============
A tree with patch implementing this WL can be found here:
bzr+ssh://myrepo.no.oracle.com/bzrroot/rsomla/mysql-wl6226
If configured with WITH_TEST_TRACE_PLUGIN build option, it will build libmysql
with built-in, pre-loaded test trace plugin and all the standard clients will
use this plugin. Test plugin will produce diagnostic output on stderr if
MYSQL_TEST_TRACE_DEBUG environment variable is set to something else than 0.
Note: Test trace plugin can disclose passwords and other security sensitive
information in the diagnostic output.
Example output
--------------
test_trace: Test trace plugin initialized
test_trace: Starting tracing in stage CONNECTING
test_trace: stage: CONNECTING, event: CONNECTING
test_trace: stage: CONNECTING, event: ERROR
test_trace: error 2038: Can't open shared memory; client could not create reque
t event (0)
test_trace: stage: CONNECTING, event: CONNECTED
test_trace: stage: WAIT_FOR_INIT_PACKET, event: READ_PACKET
test_trace: stage: WAIT_FOR_INIT_PACKET, event: PACKET_RECEIVED
test_trace: packet received: 74 bytes
0A 35 2E 35 2E 32 37 00 51 00 00 00 28 3F 43 73 .5.5.27.Q...(?Cs
3C 6C 2E 3F 00 FF F7 21 02 00 0F 80 15 00 00 00
Copyright (c) 2000, 2025, Oracle Corporation and/or its affiliates. All rights reserved.