diff --git a/CMakeLists.txt b/CMakeLists.txt index c16d8a09e..eef89a85f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,9 @@ -cmake_minimum_required(VERSION 2.6) +if(PACKAGE) + cmake_minimum_required(VERSION 2.8.12) +else() + cmake_minimum_required(VERSION 2.8) +endif() + message(STATUS "CMake version: ${CMAKE_VERSION}") include(macros.cmake) @@ -126,7 +131,6 @@ install(FILES ${ERRMSG} DESTINATION mysql) install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION .) install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION .) install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION .) -install(FILES ${CMAKE_SOURCE_DIR}/SETUP DESTINATION .) install(DIRECTORY DESTINATION log) # Install startup scripts and ldconfig files diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md new file mode 100644 index 000000000..ffba35c00 --- /dev/null +++ b/Documentation/Changelog.md @@ -0,0 +1,14 @@ +#Changelog + +These are the changes introduced in MaxScale version 1.0.6 + +* New modules added + * Binlog router + * Firewall filter + * Multi-Master monitor + * RabbitMQ logging filter +* Added option to use high precision timestamps in logging +* Readwritesplit router now returns the master server's response +* Minimum required CMake version is now 2.8.12 for package building. +* Session idle timeout added for services +* The logs can now be configured to have millisecond precision diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index a7b3d2777..7a5fb3e5b 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -6,6 +6,7 @@ - [About MaxScale](About/About-MaxScale.md) - [Release Notes 1.0.4](About/MaxScale-1.0.4-Release-Notes.md) + - [Changelog](Changelog.md) - [Limitations](About/Limitations.md) - [COPYRIGHT](About/COPYRIGHT.md) - [LICENSE](About/LICENSE.md) @@ -30,6 +31,7 @@ - [Filter Tutorial](Tutorials/Filter-Tutorial.md) - [Galera Cluster Connection Routing Tutorial](Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md) - [Galera Cluster Read-Write Splitting Tutorial](Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md) + - [MaxScale Information Schema Tutorial](Tutorials/MaxScale-Informaton-Schema.md) - [MySQL Replication Connection Routing Tutorial](Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md) - [MySQL Replication Read-Write Splitting Tutorial](Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md) - [MySQL Cluster Setup](Tutorials/MySQL-Cluster-Setup.md) diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index dc42696db..26129cd09 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -56,6 +56,16 @@ threads=1 It should be noted that additional threads will be created to execute other internal services within MaxScale. This setting is used to configure the number of threads that will be used to manage the user connections. +### `ms_timestamp` + +Enable or disable the high precision timestamps in logfiles. Enabling this adds millisecond precision to all logfile timestamps. + +``` +# Valid options are: +# ms_timestamp=<0|1> +ms_timestamp=1 +``` + ### `log_messages` Enable or disable logging of status messages. This logfile is enabled by default and contains information about the modules MaxScale is using and details about the configuration. @@ -797,6 +807,12 @@ When value all is used, queries reading session variables can be routed to any a In above-mentioned case the user-defined variable would only be updated in the master where query would be routed due to `INSERT` statement. +`max_sescmd_history` sets a limit on how many session commands each session can execute before the connection is closed. The default is an unlimited number of session commands. + + max_sescmd_history=1500 + +When a limitation is set, it effectively creates a cap on the session's memory consumption. This might be useful if connection pooling is used and the sessions use large amounts of session commands. + An example of Read/Write Split router configuration : ``` diff --git a/Documentation/Tutorials/MaxScale-Information-Schema.md b/Documentation/Tutorials/MaxScale-Information-Schema.md new file mode 100644 index 000000000..d4cdaa2c7 --- /dev/null +++ b/Documentation/Tutorials/MaxScale-Information-Schema.md @@ -0,0 +1,512 @@ +# MaxInfo Plugin +The maxinfo plugin is a special router plugin similar to the one used for implementing the server side component of the MaxAdmin interface. The plugin is designed to return data regarding the internals of MaxScale, it provides an information schema approach to monitoring the internals of MaxScale itself. + +The plugin is capable of returning data in one of two ways, either as MySQL result sets or as JSON encoded data. The choice of which mechanism used to return the data is determined by the type of the request the router receives. If a MySQL command is received then the router will return the results as a MySQL result set, if an HTTP request is received then the data will be returned as a JSON document. + +# Configuration + +The plugin is configured in the MaxScale.cnf plugin in much the same way as any other router service is configured, there needs to be a service section in the configuration file and also listeners defined for that service. The service does not however require any backend servers to be associated with it, or any monitors. + +The service entry needs to define the service name, the type as service and the router module to load. +The specified user, with the password (plain or encrypted via maxpassword utility) is allowed to connect via MySQL protocol. +Currently the user can connect to maxinfo from any remote IP and to localhost as well. + + [MaxInfo] + type=service + router=maxinfo + user=monitor + passwd=EBD2F49C3B375812A8CDEBA632ED8BBC + +The listener section defines the protocol, port and other information needed to create a listener for the service. To listen on a port using the MySQL protocol a section as shown below should be added to the configuration file. + + [MaxInfo Listener] + type=listener + service=MaxInfo + protocol=MySQLClient + port=9003 + +To listen with the HTTP protocol and hence return JSON documents a section as should below is required. + + [MaxInfo JSON Listener] + type=listener + service=MaxInfo + protocol=HTTPD + port=8003 + +If both the MySQL and JSON responses are required then a single service can be configured with both types of listener. + +As with any other listeners within MaxScale the listeners can be bound to a particular interface by use of the address= parameter. This allows the access to the maxinfo data to be limited to the localhost by adding an address=localhost parameter in the configuration file. + + [MaxInfo Listener] + type=listener + service=MaxInfo + protocol=MySQLClient + address=localhost + port=9003 + +# MySQL Interface to maxinfo + +The maxinfo supports a small subset of SQL statements in addition to the MySQL status and ping requests. These may be used for simple monitoring of MaxScale. + + % mysqladmin -hmaxscale.mariadb.com -P9003 -umonitor -pxyz ping + mysqld is alive + % mysqladmin -hmaxscale.mariadb.com -P9003 -umonitor -pxyz status + Uptime: 72 Threads: 1 Sessions: 11 + % + +The SQL command used to interact with maxinfo is the show command, a variety of show commands are available and will be described in the following sections. + +## Show variables + +The show variables command will display a set of name and value pairs for a number of MaxScale system variables. + + mysql> show variables; + +--------------------+-------------------------+ + | Variable_name | Value | + +--------------------+-------------------------+ + | version | 1.0.6-unstable | + | version_comment | MariaDB MaxScale | + | basedir | /home/mriddoch/skygate2 | + | MAXSCALE_VERSION | 1.0.6-unstable | + | MAXSCALE_THREADS | 1 | + | MAXSCALE_NBPOLLS | 3 | + | MAXSCALE_POLLSLEEP | 1000 | + | MAXSCALE_UPTIME | 223 | + | MAXSCALE_SESSIONS | 11 | + +--------------------+-------------------------+ + 9 rows in set (0.02 sec) + + mysql> + +The show variables command can also accept a limited like clause. This like clause must either be a literal string to match, a pattern starting with a %, a pattern ending with a % or a string with a % at both the start and the end. + + mysql> show variables like 'version'; + +---------------+----------------+ + | Variable_name | Value | + +---------------+----------------+ + | version | 1.0.6-unstable | + +---------------+----------------+ + 1 row in set (0.02 sec) + + mysql> show variables like 'version%'; + +-----------------+------------------+ + | Variable_name | Value | + +-----------------+------------------+ + | version | 1.0.6-unstable | + | version_comment | MariaDB MaxScale | + +-----------------+------------------+ + 2 rows in set (0.02 sec) + + mysql> show variables like '%comment'; + +-----------------+------------------+ + | Variable_name | Value | + +-----------------+------------------+ + | version_comment | MariaDB MaxScale | + +-----------------+------------------+ + 1 row in set (0.02 sec) + + mysql> show variables like '%ers%'; + +------------------+------------------+ + | Variable_name | Value | + +------------------+------------------+ + | version | 1.0.6-unstable | + | version_comment | MariaDB MaxScale | + | MAXSCALE_VERSION | 1.0.6-unstable | + +------------------+------------------+ + 3 rows in set (0.02 sec) + + mysql> + +## Show status + +The show status command displays a set of status counters, as with show variables the show status command can be passed a simplifed like clause to limit the values returned. + + mysql> show status; + +---------------------------+-------+ + | Variable_name | Value | + +---------------------------+-------+ + | Uptime | 156 | + | Uptime_since_flush_status | 156 | + | Threads_created | 1 | + | Threads_running | 1 | + | Threadpool_threads | 1 | + | Threads_connected | 11 | + | Connections | 11 | + | Client_connections | 2 | + | Backend_connections | 0 | + | Listeners | 9 | + | Zombie_connections | 0 | + | Internal_descriptors | 2 | + | Read_events | 22 | + | Write_events | 24 | + | Hangup_events | 0 | + | Error_events | 0 | + | Accept_events | 2 | + | Event_queue_length | 1 | + | Pending_events | 0 | + | Max_event_queue_length | 1 | + | Max_event_queue_time | 0 | + | Max_event_execution_time | 0 | + +---------------------------+-------+ + 22 rows in set (0.02 sec) + + mysql> + +## Show services + +The show services command will return a set of basic statistics regarding each of the configured services within MaxScale. + + mysql> show services; + +----------------+----------------+--------------+----------------+ + | Service Name | Router Module | No. Sessions | Total Sessions | + +----------------+----------------+--------------+----------------+ + | Test Service | readconnroute | 1 | 1 | + | Split Service | readwritesplit | 1 | 1 | + | Filter Service | readconnroute | 1 | 1 | + | Named Service | readwritesplit | 1 | 1 | + | QLA Service | readconnroute | 1 | 1 | + | Debug Service | debugcli | 1 | 1 | + | CLI | cli | 1 | 1 | + | MaxInfo | maxinfo | 4 | 4 | + +----------------+----------------+--------------+----------------+ + 8 rows in set (0.02 sec) + + mysql> + +The show services command does not accept a like clause and will ignore any like clause that is given. + +## Show listeners + +The show listeners command will return a set of status information for every listener defined within the MaxScale configuration file. + + mysql> show listeners; + +----------------+-----------------+-----------+------+---------+ + | Service Name | Protocol Module | Address | Port | State | + +----------------+-----------------+-----------+------+---------+ + | Test Service | MySQLClient | * | 4006 | Running | + | Split Service | MySQLClient | * | 4007 | Running | + | Filter Service | MySQLClient | * | 4008 | Running | + | Named Service | MySQLClient | * | 4010 | Running | + | QLA Service | MySQLClient | * | 4009 | Running | + | Debug Service | telnetd | localhost | 4242 | Running | + | CLI | maxscaled | localhost | 6603 | Running | + | MaxInfo | MySQLClient | * | 9003 | Running | + | MaxInfo | HTTPD | * | 8003 | Running | + +----------------+-----------------+-----------+------+---------+ + 9 rows in set (0.02 sec) + + mysql> + +The show listeners command will ignore any like clause passed to it. + +## Show sessions + +The show sessions command returns information on every active session within MaxScale. It will ignore any like clause passed to it. + + mysql> show sessions; + +-----------+---------------+----------------+---------------------------+ + | Session | Client | Service | State | + +-----------+---------------+----------------+---------------------------+ + | 0x1a92a60 | 127.0.0.1 | MaxInfo | Session ready for routing | + | 0x1a92100 | 80.240.130.35 | MaxInfo | Session ready for routing | + | 0x1a76a00 | | MaxInfo | Listener Session | + | 0x1a76020 | | MaxInfo | Listener Session | + | 0x1a75d40 | | CLI | Listener Session | + | 0x1a75220 | | Debug Service | Listener Session | + | 0x1a774b0 | | QLA Service | Listener Session | + | 0x1a78630 | | Named Service | Listener Session | + | 0x1a60270 | | Filter Service | Listener Session | + | 0x1a606f0 | | Split Service | Listener Session | + | 0x19b0380 | | Test Service | Listener Session | + +-----------+---------------+----------------+---------------------------+ + 11 rows in set (0.02 sec) + + mysql> + +## Show clients + +The show clients command reports a row for every client application connected to MaxScale. Like clauses are not available of the show clients command. + + mysql> show clients; + +-----------+---------------+---------+---------------------------+ + | Session | Client | Service | State | + +-----------+---------------+---------+---------------------------+ + | 0x1a92a60 | 127.0.0.1 | MaxInfo | Session ready for routing | + | 0x1a92100 | 80.240.130.35 | MaxInfo | Session ready for routing | + +-----------+---------------+---------+---------------------------+ + 2 rows in set (0.02 sec) + + mysql> + +## Show servers + +The show servers command returns data for each backend server configured within the MaxScale configuration file. This data includes the current number of connections MaxScale has to that server and the state of that server as monitored by MaxScale. + + mysql> show servers; + +---------+-----------+------+-------------+---------+ + | Server | Address | Port | Connections | Status | + +---------+-----------+------+-------------+---------+ + | server1 | 127.0.0.1 | 3306 | 0 | Running | + | server2 | 127.0.0.1 | 3307 | 0 | Down | + | server3 | 127.0.0.1 | 3308 | 0 | Down | + | server4 | 127.0.0.1 | 3309 | 0 | Down | + +---------+-----------+------+-------------+---------+ + 4 rows in set (0.02 sec) + + mysql> + +## Show modules + +The show modules command reports the information on the modules currently loaded into MaxScale. This includes the name type and version of each module. It also includes the API version the module has been written against and the current release status of the module. + + mysql> show modules; + +----------------+-------------+---------+-------------+----------------+ + | Module Name | Module Type | Version | API Version | Status | + +----------------+-------------+---------+-------------+----------------+ + | HTTPD | Protocol | V1.0.1 | 1.0.0 | In Development | + | maxscaled | Protocol | V1.0.0 | 1.0.0 | GA | + | telnetd | Protocol | V1.0.1 | 1.0.0 | GA | + | MySQLClient | Protocol | V1.0.0 | 1.0.0 | GA | + | mysqlmon | Monitor | V1.4.0 | 1.0.0 | GA | + | readwritesplit | Router | V1.0.2 | 1.0.0 | GA | + | readconnroute | Router | V1.1.0 | 1.0.0 | GA | + | debugcli | Router | V1.1.1 | 1.0.0 | GA | + | cli | Router | V1.0.0 | 1.0.0 | GA | + | maxinfo | Router | V1.0.0 | 1.0.0 | Alpha | + +----------------+-------------+---------+-------------+----------------+ + 10 rows in set (0.02 sec) + + mysql> + + +## Show monitors + +The show monitors command reports each monitor configured within the system and the state of that monitor. + + mysql> show monitors; + +---------------+---------+ + | Monitor | Status | + +---------------+---------+ + | MySQL Monitor | Running | + +---------------+---------+ + 1 row in set (0.02 sec) + + mysql> + + +## Show eventTimes + +The show eventTimes command returns a table of statistics that reflect the performance of the event queuing and execution portion of the MaxScale core. + + mysql> show eventTimes; + +---------------+-------------------+---------------------+ + | Duration | No. Events Queued | No. Events Executed | + +---------------+-------------------+---------------------+ + | < 100ms | 460 | 456 | + | 100 - 200ms | 0 | 3 | + | 200 - 300ms | 0 | 0 | + | 300 - 400ms | 0 | 0 | + | 400 - 500ms | 0 | 0 | + | 500 - 600ms | 0 | 0 | + | 600 - 700ms | 0 | 0 | + | 700 - 800ms | 0 | 0 | + | 800 - 900ms | 0 | 0 | + | 900 - 1000ms | 0 | 0 | + | 1000 - 1100ms | 0 | 0 | + | 1100 - 1200ms | 0 | 0 | + | 1200 - 1300ms | 0 | 0 | + | 1300 - 1400ms | 0 | 0 | + | 1400 - 1500ms | 0 | 0 | + | 1500 - 1600ms | 0 | 0 | + | 1600 - 1700ms | 0 | 0 | + | 1700 - 1800ms | 0 | 0 | + | 1800 - 1900ms | 0 | 0 | + | 1900 - 2000ms | 0 | 0 | + | 2000 - 2100ms | 0 | 0 | + | 2100 - 2200ms | 0 | 0 | + | 2200 - 2300ms | 0 | 0 | + | 2300 - 2400ms | 0 | 0 | + | 2400 - 2500ms | 0 | 0 | + | 2500 - 2600ms | 0 | 0 | + | 2600 - 2700ms | 0 | 0 | + | 2700 - 2800ms | 0 | 0 | + | 2800 - 2900ms | 0 | 0 | + | > 3000ms | 0 | 0 | + +---------------+-------------------+---------------------+ + 30 rows in set (0.02 sec) + + mysql> + +Each row represents a time interval, in 100ms increments, with the counts representing the number of events that were in the event queue for the length of time that row represents and the number of events that were executing of the time indicated by the row. + +# JSON Interface + +The simplified JSON interface takes the URL of the request made to maxinfo and maps that to a show command in the above section. + +## Variables + +The /variables URL will return the MaxScale variables, these variables can not be filtered via this interface. + + $ curl http://maxscale.mariadb.com:8003/variables + [ { "Variable_name" : "version", "Value" : "1.0.6-unstable"}, + { "Variable_name" : "version_comment", "Value" : "MariaDB MaxScale"}, + { "Variable_name" : "basedir", "Value" : "/home/mriddoch/skygate2"}, + { "Variable_name" : "MAXSCALE_VERSION", "Value" : "1.0.6-unstable"}, + { "Variable_name" : "MAXSCALE_THREADS", "Value" : 1}, + { "Variable_name" : "MAXSCALE_NBPOLLS", "Value" : 3}, + { "Variable_name" : "MAXSCALE_POLLSLEEP", "Value" : 1000}, + { "Variable_name" : "MAXSCALE_UPTIME", "Value" : 3948}, + { "Variable_name" : "MAXSCALE_SESSIONS", "Value" : 12}] + $ + +## Status + +Use of the /status URI will return the status information that would normally be returned by the show status command. No filtering of the status information is available via this interface + + $ curl http://maxscale.mariadb.com:8003/status + [ { "Variable_name" : "Uptime", "Value" : 3831}, + { "Variable_name" : "Uptime_since_flush_status", "Value" : 3831}, + { "Variable_name" : "Threads_created", "Value" : 1}, + { "Variable_name" : "Threads_running", "Value" : 1}, + { "Variable_name" : "Threadpool_threads", "Value" : 1}, + { "Variable_name" : "Threads_connected", "Value" : 12}, + { "Variable_name" : "Connections", "Value" : 12}, + { "Variable_name" : "Client_connections", "Value" : 3}, + { "Variable_name" : "Backend_connections", "Value" : 0}, + { "Variable_name" : "Listeners", "Value" : 9}, + { "Variable_name" : "Zombie_connections", "Value" : 0}, + { "Variable_name" : "Internal_descriptors", "Value" : 3}, + { "Variable_name" : "Read_events", "Value" : 469}, + { "Variable_name" : "Write_events", "Value" : 479}, + { "Variable_name" : "Hangup_events", "Value" : 12}, + { "Variable_name" : "Error_events", "Value" : 0}, + { "Variable_name" : "Accept_events", "Value" : 15}, + { "Variable_name" : "Event_queue_length", "Value" : 1}, + { "Variable_name" : "Pending_events", "Value" : 0}, + { "Variable_name" : "Max_event_queue_length", "Value" : 1}, + { "Variable_name" : "Max_event_queue_time", "Value" : 0}, + { "Variable_name" : "Max_event_execution_time", "Value" : 1}] + $ + +## Services + +The /services URI returns the data regarding the services defined within the configuration of MaxScale. Two counters are returned, the current number of sessions attached to this service and the total number connected since the service started. + + $ curl http://maxscale.mariadb.com:8003/services + [ { "Service Name" : "Test Service", "Router Module" : "readconnroute", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "Split Service", "Router Module" : "readwritesplit", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "Filter Service", "Router Module" : "readconnroute", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "Named Service", "Router Module" : "readwritesplit", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "QLA Service", "Router Module" : "readconnroute", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "Debug Service", "Router Module" : "debugcli", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "CLI", "Router Module" : "cli", "No. Sessions" : 1, "Total Sessions" : 1}, + { "Service Name" : "MaxInfo", "Router Module" : "maxinfo", "No. Sessions" : 5, "Total Sessions" : 20}] + $ + +## Listeners + +The /listeners URI will return a JSON array with one entry per listener, each entry is a JSON object that describes the configuration and state of that listener. + + $ curl http://maxscale.mariadb.com:8003/listeners + [ { "Service Name" : "Test Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4006, "State" : "Running"}, + { "Service Name" : "Split Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4007, "State" : "Running"}, + { "Service Name" : "Filter Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4008, "State" : "Running"}, + { "Service Name" : "Named Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4010, "State" : "Running"}, + { "Service Name" : "QLA Service", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 4009, "State" : "Running"}, + { "Service Name" : "Debug Service", "Protocol Module" : "telnetd", "Address" : "localhost", "Port" : 4242, "State" : "Running"}, + { "Service Name" : "CLI", "Protocol Module" : "maxscaled", "Address" : "localhost", "Port" : 6603, "State" : "Running"}, + { "Service Name" : "MaxInfo", "Protocol Module" : "MySQLClient", "Address" : "*", "Port" : 9003, "State" : "Running"}, + { "Service Name" : "MaxInfo", "Protocol Module" : "HTTPD", "Address" : "*", "Port" : 8003, "State" : "Running"}] + $ + +## Modules + +The /modules URI returns data for each plugin that has been loaded into MaxScale. The plugin name, type and version are returned as is the version of the plugin API that the plugin was built against and the release status of the plugin. + + $ curl http://maxscale.mariadb.com:8003/modules + [ { "Module Name" : "HTTPD", "Module Type" : "Protocol", "Version" : "V1.0.1", "API Version" : "1.0.0", "Status" : "In Development"}, + { "Module Name" : "maxscaled", "Module Type" : "Protocol", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "telnetd", "Module Type" : "Protocol", "Version" : "V1.0.1", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "MySQLClient", "Module Type" : "Protocol", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "mysqlmon", "Module Type" : "Monitor", "Version" : "V1.4.0", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "readwritesplit", "Module Type" : "Router", "Version" : "V1.0.2", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "readconnroute", "Module Type" : "Router", "Version" : "V1.1.0", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "debugcli", "Module Type" : "Router", "Version" : "V1.1.1", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "cli", "Module Type" : "Router", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "GA"}, + { "Module Name" : "maxinfo", "Module Type" : "Router", "Version" : "V1.0.0", "API Version" : "1.0.0", "Status" : "Alpha"}] + $ + +## Sessions + +The /sessions URI returns a JSON array with an object for each active session within MaxScale. + + $ curl http://maxscale.mariadb.com:8003/sessions + [ { "Session" : "0x1a8e9a0", "Client" : "80.176.79.245", "Service" : "MaxInfo", "State" : "Session ready for routing"}, + { "Session" : "0x1a8e6d0", "Client" : "80.240.130.35", "Service" : "MaxInfo", "State" : "Session ready for routing"}, + { "Session" : "0x1a8ddd0", "Client" : , "Service" : "MaxInfo", "State" : "Listener Session"}, + { "Session" : "0x1a92da0", "Client" : , "Service" : "MaxInfo", "State" : "Listener Session"}, + { "Session" : "0x1a92ac0", "Client" : , "Service" : "CLI", "State" : "Listener Session"}, + { "Session" : "0x1a70e90", "Client" : , "Service" : "Debug Service", "State" : "Listener Session"}, + { "Session" : "0x1a758d0", "Client" : , "Service" : "QLA Service", "State" : "Listener Session"}, + { "Session" : "0x1a73a90", "Client" : , "Service" : "Named Service", "State" : "Listener Session"}, + { "Session" : "0x1a5c0b0", "Client" : , "Service" : "Filter Service", "State" : "Listener Session"}, + { "Session" : "0x1a5c530", "Client" : , "Service" : "Split Service", "State" : "Listener Session"}, + { "Session" : "0x19ac1c0", "Client" : , "Service" : "Test Service", "State" : "Listener Session"}] + $ + +## Clients + +The /clients URI is a limited version of the /sessions, in this case it only returns an entry for a session that represents a client connection. + + $ curl http://maxscale.mariadb.com:8003/clients + [ { "Session" : "0x1a90be0", "Client" : "80.176.79.245", "Service" : "MaxInfo", "State" : "Session ready for routing"}, + { "Session" : "0x1a8e9a0", "Client" : "127.0.0.1", "Service" : "MaxInfo", "State" : "Session ready for routing"}, + { "Session" : "0x1a8e6d0", "Client" : "80.240.130.35", "Service" : "MaxInfo", "State" : "Session ready for routing"}] + $ + +## Servers + +The /servers URI is used to retrieve information for each of the servers defined within the MaxScale configuration. This information includes the connection count and the current status as monitored by MaxScale. The connection count is only those connections made by MaxScale to those servers. + + $ curl http://maxscale.mariadb.com:8003/servers + [ { "Server" : "server1", "Address" : "127.0.0.1", "Port" : 3306, "Connections" : 0, "Status" : "Running"}, + { "Server" : "server2", "Address" : "127.0.0.1", "Port" : 3307, "Connections" : 0, "Status" : "Down"}, + { "Server" : "server3", "Address" : "127.0.0.1", "Port" : 3308, "Connections" : 0, "Status" : "Down"}, + { "Server" : "server4", "Address" : "127.0.0.1", "Port" : 3309, "Connections" : 0, "Status" : "Down"}] + $ + +## Event Times + +The /event/times URI returns an array of statistics that reflect the performance of the event queuing and execution portion of the MaxScale core. Each element is an object that represents a time bucket, in 100ms increments, with the counts representing the number of events that were in the event queue for the length of time that row represents and the number of events that were executing of the time indicated by the object. + + $ curl http://maxscale.mariadb.com:8003/event/times + [ { "Duration" : "< 100ms", "No. Events Queued" : 64, "No. Events Executed" : 63}, + { "Duration" : " 100 - 200ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 200 - 300ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 300 - 400ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 400 - 500ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 500 - 600ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 600 - 700ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 700 - 800ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 800 - 900ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : " 900 - 1000ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1000 - 1100ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1100 - 1200ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1200 - 1300ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1300 - 1400ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1400 - 1500ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1500 - 1600ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1600 - 1700ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1700 - 1800ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1800 - 1900ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "1900 - 2000ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2000 - 2100ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2100 - 2200ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2200 - 2300ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2300 - 2400ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2400 - 2500ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2500 - 2600ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2600 - 2700ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2700 - 2800ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "2800 - 2900ms", "No. Events Queued" : 0, "No. Events Executed" : 0}, + { "Duration" : "> 3000ms", "No. Events Queued" : 0, "No. Events Executed" : 0}] diff --git a/Documentation/filters/Tee-Filter.md b/Documentation/filters/Tee-Filter.md index be77e83b8..777b16fa9 100644 --- a/Documentation/filters/Tee-Filter.md +++ b/Documentation/filters/Tee-Filter.md @@ -6,7 +6,7 @@ The tee filter is a filter module for MaxScale is a "plumbing" fitting in the Ma # Configuration -The configuration block for the TEE filter requires the minimal filter parameters in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf, that defines the filter to load and the service to send the duplicates to. +The configuration block for the TEE filter requires the minimal filter parameters in it’s section within the MaxScale.cnf file, stored in $MAXSCALE_HOME/etc/MaxScale.cnf, that defines the filter to load and the service to send the duplicates to. Currently the tee filter does not support multi-statements. [DataMartFilter] diff --git a/README b/README index d105075ae..8d3c08a13 100644 --- a/README +++ b/README @@ -29,7 +29,7 @@ issues and communicate with the MaxScale community. or use the [forum](http://groups.google.com/forum/#!forum/maxscale) interface Bugs can be reported in the MariaDB Corporation bugs database - [bug.mariadb.com](http://bugs.mariadb.com) + [https://mariadb.atlassian.net/secure/CreateIssue!default.jspa](https://mariadb.atlassian.net/secure/CreateIssue!default.jspa) under project MXS. \section Documentation diff --git a/SETUP b/SETUP deleted file mode 100644 index 4e51bef5d..000000000 --- a/SETUP +++ /dev/null @@ -1,33 +0,0 @@ -Installation and startup - -Untar the binary distribution in the desired location, -e.g. /usr/local/skysql - -Alternatively build from the source code using the instructions -in the README file and execute make install. - -Simply set the environment variable MAXSCALE_HOME to point to the -MaxScale directory, found inside the path into which the files have been copied, -e.g. MAXSCALE_HOME=/usr/local/skysql/maxscale - -Also you will need to optionaly set LD_LIBRARY_PATH to include the 'lib' folder, -found inside the path into which the files have been copied, -e.g. LD_LIBRARY_PATH=/usr/local/skysql/maxscale/lib - -To start MaxScale execute the command 'maxscale' from the bin folder, -e.g. /usr/local/skysql/maxscale/bin/maxscale or by using the -installed init.d script. - -Configuration - -You need to create and edit the file MaxScale.cnf in $MAXSCALE_HOME/etc. -You should define the set of server definitions you require, with the addresses -and ports of those servers. Also define the listening ports for your -various services. - -In order to view the internal activity of MaxScale you can telnet to -the port defined for the telnet listener. Initially you may login with -the user name of "admin" and the password "skysql". Once connected type -help for an overview of the commands and help for the more -detailed help on commands. Use the 'add user' command to add a new user -which will also remove the admin/skysql user. diff --git a/etc/ubuntu/init.d/maxscale.in b/etc/ubuntu/init.d/maxscale.in index 269f88638..21a4e425e 100755 --- a/etc/ubuntu/init.d/maxscale.in +++ b/etc/ubuntu/init.d/maxscale.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # maxscale: The MariaDB Corporation MaxScale database proxy # @@ -58,7 +58,7 @@ RETVAL=0 start() { log_daemon_msg "Starting MaxScale" - start_daemon -p $MAXSCALE_PIDFILE $DAEMON 2> /dev/null + start_daemon -p $MAXSCALE_PIDFILE $DAEMON 2> /dev/null > /dev/null sleep 2 diff --git a/log_manager/log_manager.cc b/log_manager/log_manager.cc index 89f046465..4179f5c49 100644 --- a/log_manager/log_manager.cc +++ b/log_manager/log_manager.cc @@ -51,6 +51,7 @@ static int block_start_index; static int prevval; static simple_mutex_t msg_mutex; #endif +static int highprec = 0; /** * Variable holding the enabled logfiles information. * Used from log users to check enabled logs prior calling @@ -696,9 +697,11 @@ static int logmanager_write_log( else { sesid_str_len = 0; - } - timestamp_len = get_timestamp_len(); - + } + if(highprec) + timestamp_len = get_timestamp_len_hp(); + else + timestamp_len = get_timestamp_len(); cmplen = sesid_str_len > 0 ? sesid_str_len - sizeof(char) : 0; /** Find out how much can be safely written with current block size */ @@ -758,8 +761,10 @@ static int logmanager_write_log( * to wp. * Returned timestamp_len doesn't include terminating null. */ - timestamp_len = snprint_timestamp(wp, timestamp_len); - + if(highprec) + timestamp_len = snprint_timestamp_hp(wp, timestamp_len); + else + timestamp_len = snprint_timestamp(wp, timestamp_len); if (sesid_str_len != 0) { /** @@ -3077,3 +3082,8 @@ void skygw_log_sync_all(void) skygw_message_send(lm->lm_logmes); skygw_message_wait(lm->lm_clientmes); } + +void skygw_set_highp(int val) +{ + highprec = val; +} \ No newline at end of file diff --git a/log_manager/log_manager.h b/log_manager/log_manager.h index f2146d303..10ccdea1c 100644 --- a/log_manager/log_manager.h +++ b/log_manager/log_manager.h @@ -119,7 +119,7 @@ int skygw_log_write_flush(logfile_id_t id, const char* format, ...); int skygw_log_enable(logfile_id_t id); int skygw_log_disable(logfile_id_t id); void skygw_log_sync_all(void); - +void skygw_set_highp(int); EXTERN_C_BLOCK_END const char* get_trace_prefix_default(void); diff --git a/server/MaxScale_template.cnf b/server/MaxScale_template.cnf index 90d994520..feec5695d 100644 --- a/server/MaxScale_template.cnf +++ b/server/MaxScale_template.cnf @@ -182,6 +182,12 @@ replace=select # router_options= # slave_selection_criteria=[LEAST_CURRENT_OPERATIONS|LEAST_BEHIND_MASTER] # +# router_options=max_sescmd_history specifies a limit on the number of 'session commands' +# a single session can execute. Please refer to the configuration guide for more details - optional. +# +# router_options= +# max_sescmd_history=2500 +# # max_slave_connections specifies how many slaves a router session can # connect to - optional. # diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index b60999901..fbfccbaf0 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -7,7 +7,7 @@ add_executable(maxscale atomic.c buffer.c spinlock.c gateway.c gw_utils.c utils.c dcb.c load_utils.c session.c service.c server.c poll.c config.c users.c hashtable.c dbusers.c thread.c gwbitmask.c monitor.c adminusers.c secrets.c filter.c modutil.c hint.c - housekeeper.c memlog.c) + housekeeper.c memlog.c resultset.c) target_link_libraries(maxscale ${EMBEDDED_LIB} log_manager utils ssl aio pthread crypt dl crypto inih z rt m stdc++) install(TARGETS maxscale DESTINATION bin) diff --git a/server/core/config.c b/server/core/config.c index f376d229e..aec9a5b13 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -1247,6 +1247,10 @@ int i; { gateway.pollsleep = atoi(value); } + else if (strcmp(name, "ms_timestamp") == 0) + { + skygw_set_highp(atoi(value)); + } else { for (i = 0; lognames[i].logname; i++) diff --git a/server/core/dcb.c b/server/core/dcb.c index a732ed0ff..5c5d0a81d 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -803,7 +803,8 @@ int dcb_read( if (r <= 0 && l_errno != EAGAIN && - l_errno != EWOULDBLOCK) + l_errno != EWOULDBLOCK && + l_errno != 0) { n = -1; goto return_n; @@ -1586,8 +1587,10 @@ va_list args; int dcb_isclient(DCB *dcb) { - if(dcb->session) { - if (dcb->session->client) { + if (dcb->state != DCB_STATE_LISTENING && dcb->session) + { + if (dcb->session->client) + { return (dcb->session && dcb == dcb->session->client); } } @@ -2172,3 +2175,52 @@ dcb_null_auth(DCB *dcb, SERVER *server, SESSION *session, GWBUF *buf) { return 0; } + +/** + * Return DCB counts optionally filtered by usage + * + * @param usage The usage of the DCB + * @return A count of DCBs in the desired state + */ +int +dcb_count_by_usage(DCB_USAGE usage) +{ +int rval = 0; +DCB *ptr; + + spinlock_acquire(&dcbspin); + ptr = allDCBs; + while (ptr) + { + switch (usage) + { + case DCB_USAGE_CLIENT: + if (dcb_isclient(ptr)) + rval++; + break; + case DCB_USAGE_LISTENER: + if (ptr->state == DCB_STATE_LISTENING) + rval++; + break; + case DCB_USAGE_BACKEND: + if (dcb_isclient(ptr) == 0 + && ptr->dcb_role == DCB_ROLE_REQUEST_HANDLER) + rval++; + break; + case DCB_USAGE_INTERNAL: + if (ptr->dcb_role == DCB_ROLE_REQUEST_HANDLER) + rval++; + break; + case DCB_USAGE_ZOMBIE: + if (DCB_ISZOMBIE(ptr)) + rval++; + break; + case DCB_USAGE_ALL: + rval++; + break; + } + ptr = ptr->next; + } + spinlock_release(&dcbspin); + return rval; +} diff --git a/server/core/gateway.c b/server/core/gateway.c index 1221cee3b..0d1c9cbf5 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -79,6 +79,8 @@ # define _GNU_SOURCE #endif +time_t MaxScaleStarted; + extern char *program_invocation_name; extern char *program_invocation_short_name; @@ -992,7 +994,7 @@ static void usage(void) " -f|--config=... relative|absolute pathname of MaxScale configuration file\n" " (default: $MAXSCALE_HOME/etc/MaxScale.cnf)\n" " -l|--log=... log to file or shared memory\n" - " -lfile or -lshm - defaults to file\n" + " -lfile or -lshm - defaults to shared memory\n" " -v|--version print version info and exit\n" " -?|--help show this help\n" , progname); @@ -1054,7 +1056,7 @@ int main(int argc, char **argv) char* cnf_file_arg = NULL; /*< conf filename from cmd-line arg */ void* log_flush_thr = NULL; int option_index; - int logtofile = 1; /* Use shared memory or file */ + int logtofile = 0; /* Use shared memory or file */ ssize_t log_flush_timeout_ms = 0; sigset_t sigset; sigset_t sigpipe_mask; @@ -1797,6 +1799,8 @@ int main(int argc, char **argv) LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE, "MaxScale started with %d server threads.", config_threadcount()))); + + MaxScaleStarted = time(0); /*< * Serve clients. */ @@ -1951,3 +1955,9 @@ static int write_pid_file(char *home_dir) { /* success */ return 0; } + +int +MaxScaleUptime() +{ + return time(0) - MaxScaleStarted; +} diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 3fe975f5c..7f3fc587d 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -408,3 +408,80 @@ MODULES *ptr = registered; } dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n\n"); } + +/** + * Provide a row to the result set that defines the set of modules + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +moduleRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +int i = 0;; +char *stat, buf[20]; +RESULT_ROW *row; +MODULES *ptr; + + ptr = registered; + while (i < *rowno && ptr) + { + i++; + ptr = ptr->next; + } + if (ptr == NULL) + { + free(data); + return NULL; + } + (*rowno)++; + row = resultset_make_row(set); + resultset_row_set(row, 0, ptr->module); + resultset_row_set(row, 1, ptr->type); + resultset_row_set(row, 2, ptr->version); + sprintf(buf, "%d.%d.%d", ptr->info->api_version.major, + ptr->info->api_version.minor, + ptr->info->api_version.patch); + resultset_row_set(row, 3, buf); + resultset_row_set(row, 4, ptr->info->status == MODULE_IN_DEVELOPMENT + ? "In Development" + : (ptr->info->status == MODULE_ALPHA_RELEASE + ? "Alpha" + : (ptr->info->status == MODULE_BETA_RELEASE + ? "Beta" + : (ptr->info->status == MODULE_GA + ? "GA" + : (ptr->info->status == MODULE_EXPERIMENTAL + ? "Experimental" : "Unknown"))))); + return row; +} + +/** + * Return a resultset that has the current set of modules in it + * + * @return A Result set + */ +RESULTSET * +moduleGetList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(moduleRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Module Name", 18, COL_TYPE_VARCHAR); + resultset_add_column(set, "Module Type", 12, COL_TYPE_VARCHAR); + resultset_add_column(set, "Version", 10, COL_TYPE_VARCHAR); + resultset_add_column(set, "API Version", 8, COL_TYPE_VARCHAR); + resultset_add_column(set, "Status", 15, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/modutil.c b/server/core/modutil.c index e48854f55..5d6e4985f 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -63,6 +63,23 @@ unsigned char *ptr; return ptr[4] == 0x03; // COM_QUERY } +/** + * Check if a GWBUF structure is a MySQL COM_STMT_PREPARE packet + * + * @param buf Buffer to check + * @return True if GWBUF is a COM_STMT_PREPARE packet + */ +int +modutil_is_SQL_prepare(GWBUF *buf) +{ +unsigned char *ptr; + + if (GWBUF_LENGTH(buf) < 5) + return 0; + ptr = GWBUF_DATA(buf); + return ptr[4] == 0x16 ; // COM_STMT_PREPARE +} + /** * Extract the SQL portion of a COM_QUERY packet * @@ -243,7 +260,7 @@ modutil_get_SQL(GWBUF *buf) unsigned int len, length; char *ptr, *dptr, *rval = NULL; - if (!modutil_is_SQL(buf)) + if (!modutil_is_SQL(buf) && !modutil_is_SQL_prepare(buf)) return rval; ptr = GWBUF_DATA(buf); length = *ptr++; diff --git a/server/core/monitor.c b/server/core/monitor.c index 3584978e9..8ef9a1396 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -365,3 +365,66 @@ monitorSetNetworkTimeout(MONITOR *mon, int type, int value) { mon->module->setNetworkTimeout(mon->handle, type, value); } } + +/** + * Provide a row to the result set that defines the set of monitors + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +monitorRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +int i = 0;; +char buf[20]; +RESULT_ROW *row; +MONITOR *ptr; + + spinlock_acquire(&monLock); + ptr = allMonitors; + while (i < *rowno && ptr) + { + i++; + ptr = ptr->next; + } + if (ptr == NULL) + { + spinlock_release(&monLock); + free(data); + return NULL; + } + (*rowno)++; + row = resultset_make_row(set); + resultset_row_set(row, 0, ptr->name); + resultset_row_set(row, 1, ptr->state & MONITOR_STATE_RUNNING + ? "Running" : "Stopped"); + spinlock_release(&monLock); + return row; +} + +/** + * Return a resultset that has the current set of monitors in it + * + * @return A Result set + */ +RESULTSET * +monitorGetList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(monitorRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Monitor", 20, COL_TYPE_VARCHAR); + resultset_add_column(set, "Status", 10, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/poll.c b/server/core/poll.c index 351827f7c..a6e3035dc 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -30,7 +30,9 @@ #include #include #include +#include #include +#include #define PROFILE_POLL 0 @@ -151,8 +153,8 @@ static struct { int n_hup; /*< Number of hangup events */ int n_accept; /*< Number of accept events */ int n_polls; /*< Number of poll cycles */ - int n_pollev; /*< Number of polls returnign events */ - int n_nbpollev; /*< Number of polls returnign events */ + int n_pollev; /*< Number of polls returning events */ + int n_nbpollev; /*< Number of polls returning events */ int n_nothreads; /*< Number of times no threads are polling */ int n_fds[MAXNFDS]; /*< Number of wakeups with particular n_fds value */ @@ -1525,3 +1527,104 @@ int i; dcb_printf(pdcb, " > %2d00ms | %-10d | %-10d\n", N_QUEUE_TIMES, queueStats.qtimes[N_QUEUE_TIMES], queueStats.exectimes[N_QUEUE_TIMES]); } + +/** + * Return a poll statistic from the polling subsystem + * + * @param stat The required statistic + * @return The value of that statistic + */ +int +poll_get_stat(POLL_STAT stat) +{ + switch (stat) + { + case POLL_STAT_READ: + return pollStats.n_read; + case POLL_STAT_WRITE: + return pollStats.n_write; + case POLL_STAT_ERROR: + return pollStats.n_error; + case POLL_STAT_HANGUP: + return pollStats.n_hup; + case POLL_STAT_ACCEPT: + return pollStats.n_accept; + case POLL_STAT_EVQ_LEN: + return pollStats.evq_length; + case POLL_STAT_EVQ_PENDING: + return pollStats.evq_pending; + case POLL_STAT_EVQ_MAX: + return pollStats.evq_max; + case POLL_STAT_MAX_QTIME: + return (int)queueStats.maxqtime; + case POLL_STAT_MAX_EXECTIME: + return (int)queueStats.maxexectime; + } + return 0; +} + +/** + * Provide a row to the result set that defines the event queue statistics + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +eventTimesRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +char buf[40]; +RESULT_ROW *row; + + if (*rowno >= N_QUEUE_TIMES) + { + free(data); + return NULL; + } + row = resultset_make_row(set); + if (*rowno == 0) + resultset_row_set(row, 0, "< 100ms"); + else if (*rowno == N_QUEUE_TIMES - 1) + { + sprintf(buf, "> %2d00ms", N_QUEUE_TIMES); + resultset_row_set(row, 0, buf); + } + else + { + sprintf(buf, "%2d00 - %2d00ms", *rowno, (*rowno) + 1); + resultset_row_set(row, 0, buf); + } + sprintf(buf, "%d", queueStats.qtimes[*rowno]); + resultset_row_set(row, 1, buf); + sprintf(buf, "%d", queueStats.exectimes[*rowno]); + resultset_row_set(row, 2, buf); + (*rowno)++; + return row; +} + +/** + * Return a resultset that has the current set of services in it + * + * @return A Result set + */ +RESULTSET * +eventTimesGetList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(eventTimesRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Duration", 20, COL_TYPE_VARCHAR); + resultset_add_column(set, "No. Events Queued", 12, COL_TYPE_VARCHAR); + resultset_add_column(set, "No. Events Executed", 12, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/resultset.c b/server/core/resultset.c new file mode 100644 index 000000000..cca4e3c5a --- /dev/null +++ b/server/core/resultset.c @@ -0,0 +1,467 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file resultset.c - Implementation of a generic result set mechanism + * + * @verbatim + * Revision History + * + * Date Who Description + * 17/02/15 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include +#include + + +static int mysql_send_fieldcount(DCB *, int); +static int mysql_send_columndef(DCB *, char *, int, int, uint8_t); +static int mysql_send_eof(DCB *, int); +static int mysql_send_row(DCB *, RESULT_ROW *, int); + + +/** + * Create a generic result set + * + * @param func Function to call for each row + * @param data Data to pass to the row retrieval function + * @return An empty resultset or NULL on error + */ +RESULTSET * +resultset_create(RESULT_ROW_CB func, void *data) +{ +RESULTSET *rval; + + if ((rval = (RESULTSET *)malloc(sizeof(RESULTSET))) != NULL) + { + rval->n_cols = 0; + rval->column = NULL; + rval->userdata = data; + rval->fetchrow = func; + } + return rval; +} + +/** + * Free a previously allocated resultset + * + * @param resultset The result set to free + */ +void +resultset_free(RESULTSET *resultset) +{ +RESULT_COLUMN *col; + + if (resultset) + return; + col = resultset->column; + while (col) + { + RESULT_COLUMN *next; + + next = col->next; + resultset_column_free(col); + col = next; + } + free(resultset); +} + +/** + * Add a new column to a result set. Columns are added to the right + * of the result set, i.e. the existing order is maintained. + * + * @param set The result set + * @param name The column name + * @param len The column length + * @param type The column type + * @return The numebr of columns added to the result set + */ +int +resultset_add_column(RESULTSET *set, char *name, int len, RESULT_COL_TYPE type) +{ +RESULT_COLUMN *newcol, *ptr; + + if ((newcol = (RESULT_COLUMN *)malloc(sizeof(RESULT_COLUMN))) == NULL) + return 0; + if ((newcol->name = strdup(name)) == NULL) + { + free(newcol); + return 0; + } + newcol->type = type; + newcol->len = len; + newcol->next = NULL; + + if (set->column == NULL) + set->column = newcol; + else + { + ptr = set->column; + while (ptr->next) + ptr = ptr->next; + ptr->next = newcol; + } + set->n_cols++; + return 1; +} + +/** + * Free a result set column + * + * @param col Column to free + */ +void +resultset_column_free(RESULT_COLUMN *col) +{ + free(col->name); + free(col); +} + +/** + * Create a blank row, a row with all values NULL, for a result + * set. + * + * @param set The result set the row will be part of + * @return The NULL result set row + */ +RESULT_ROW * +resultset_make_row(RESULTSET *set) +{ +RESULT_ROW *row; +int i; + + if ((row = (RESULT_ROW *)malloc(sizeof(RESULT_ROW))) == NULL) + return NULL; + row->n_cols = set->n_cols; + if ((row->cols = (char **)malloc(row->n_cols * sizeof(char *))) == NULL) + { + free(row); + return NULL; + } + + for (i = 0; i < set->n_cols; i++) + row->cols[i] = NULL; + return row; +} + +/** + * Free a result set row. If a column in the row has a non-null values + * then the data is assumed to be a malloc'd pointer and will be free'd. + * If any value is not a malloc'd pointer it should be removed before + * making this call. + * + * @param row The row to free + */ +void +resultset_free_row(RESULT_ROW *row) +{ +int i; + + for (i = 0; i < row->n_cols; i++) + if (row->cols[i]) + free(row->cols[i]); + free(row->cols); + free(row); +} + +/** + * Add a value in a particular column of the row . The value is + * a NULL terminated string and will be copied into malloc'd + * storage by this routine. + * + * @param row The row ro add the column into + * @param col The column number (0 to n_cols - 1) + * @param value The column value, may be NULL + * @return The number of columns inserted + */ +int +resultset_row_set(RESULT_ROW *row, int col, char *value) +{ + if (col < 0 || col >= row->n_cols) + return 0; + if (value) + { + if ((row->cols[col] = strdup(value)) == NULL) + return 0; + return 1; + } + else if (row->cols[col]) + free(row->cols[col]); + row->cols[col] = NULL; + return 1; +} + +/** + * Stream a result set using the MySQL protocol for encodign the result + * set. Each row is retrieved by calling the function passed in the + * argument list. + * + * @param set The result set to stream + * @param dcb The connection to stream the result set to + */ +void +resultset_stream_mysql(RESULTSET *set, DCB *dcb) +{ +RESULT_COLUMN *col; +RESULT_ROW *row; +uint8_t seqno = 2; + + mysql_send_fieldcount(dcb, set->n_cols); + + col = set->column; + while (col) + { + mysql_send_columndef(dcb, col->name, col->type, col->len, seqno++); + col = col->next; + } + mysql_send_eof(dcb, seqno++); + while ((row = (*set->fetchrow)(set, set->userdata)) != NULL) + { + mysql_send_row(dcb, row, seqno++); + resultset_free_row(row); + } + mysql_send_eof(dcb, seqno); +} + +/** + * Send the field count packet in a response packet sequence. + * + * @param dcb DCB of connection to send result set to + * @param count Number of columns in the result set + * @return Non-zero on success + */ +static int +mysql_send_fieldcount(DCB *dcb, int count) +{ +GWBUF *pkt; +uint8_t *ptr; + + if ((pkt = gwbuf_alloc(5)) == NULL) + return 0; + ptr = GWBUF_DATA(pkt); + *ptr++ = 0x01; // Payload length + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x01; // Sequence number in response + *ptr++ = count; // Length of result string + return dcb->func.write(dcb, pkt); +} + + +/** + * Send the column definition packet in a response packet sequence. + * + * @param dcb The DCB of the connection + * @param name Name of the column + * @param type Column type + * @param len Column length + * @param seqno Packet sequence number + * @return Non-zero on success + */ +static int +mysql_send_columndef(DCB *dcb, char *name, int type, int len, uint8_t seqno) +{ +GWBUF *pkt; +uint8_t *ptr; +int plen; + + if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL) + return 0; + ptr = GWBUF_DATA(pkt); + plen = 22 + strlen(name); + *ptr++ = plen & 0xff; + *ptr++ = (plen >> 8) & 0xff; + *ptr++ = (plen >> 16)& 0xff; + *ptr++ = seqno; // Sequence number in response + *ptr++ = 3; // Catalog is always def + *ptr++ = 'd'; + *ptr++ = 'e'; + *ptr++ = 'f'; + *ptr++ = 0; // Schema name length + *ptr++ = 0; // virtual table name length + *ptr++ = 0; // Table name length + *ptr++ = strlen(name); // Column name length; + while (*name) + *ptr++ = *name++; // Copy the column name + *ptr++ = 0; // Orginal column name + *ptr++ = 0x0c; // Length of next fields always 12 + *ptr++ = 0x3f; // Character set + *ptr++ = 0; + *ptr++ = len & 0xff; // Length of column + *ptr++ = (len >> 8) & 0xff; + *ptr++ = (len >> 16) & 0xff; + *ptr++ = (len >> 24) & 0xff; + *ptr++ = type; + *ptr++ = 0x81; // Two bytes of flags + if (type == 0xfd) + *ptr++ = 0x1f; + else + *ptr++ = 0x00; + *ptr++= 0; + *ptr++= 0; + *ptr++= 0; + return dcb->func.write(dcb, pkt); +} + + +/** + * Send an EOF packet in a response packet sequence. + * + * @param dcb The client connection + * @param seqno The sequence number of the EOF packet + * @return Non-zero on success + */ +static int +mysql_send_eof(DCB *dcb, int seqno) +{ +GWBUF *pkt; +uint8_t *ptr; + + if ((pkt = gwbuf_alloc(9)) == NULL) + return 0; + ptr = GWBUF_DATA(pkt); + *ptr++ = 0x05; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = seqno; // Sequence number in response + *ptr++ = 0xfe; // Length of result string + *ptr++ = 0x00; // No Errors + *ptr++ = 0x00; + *ptr++ = 0x02; // Autocommit enabled + *ptr++ = 0x00; + return dcb->func.write(dcb, pkt); +} + + + +/** + * Send a row packet in a response packet sequence. + * + * @param dcb The client connection + * @param row The row to send + * @param seqno The sequence number of the EOF packet + * @return Non-zero on success + */ +static int +mysql_send_row(DCB *dcb, RESULT_ROW *row, int seqno) +{ +GWBUF *pkt; +int i, len = 4; +uint8_t *ptr; + + for (i = 0; i < row->n_cols; i++) + { + if (row->cols[i]) + len += strlen(row->cols[i]); + len++; + } + + if ((pkt = gwbuf_alloc(len)) == NULL) + return 0; + ptr = GWBUF_DATA(pkt); + len -= 4; + *ptr++ = len & 0xff; + *ptr++ = (len >> 8) & 0xff; + *ptr++ = (len >> 16) & 0xff; + *ptr++ = seqno; + for (i = 0; i < row->n_cols; i++) + { + if (row->cols[i]) + { + len = strlen(row->cols[i]); + *ptr++ = len; + strncpy((char *)ptr, row->cols[i], len); + ptr += len; + } + else + { + *ptr++ = 0; // NULL column + } + } + + return dcb->func.write(dcb, pkt); +} + +/** + * Return true if the string only contains numerics + * + * @param value String to test + * @return Non-zero if the string is made of of numeric values + */ +static int +value_is_numeric(char *value) +{ + while (*value) + { + if (!isdigit(*value)) + return 0; + value++; + } + return 1; +} + +/** + * Stream a result set encoding it as a JSON object + * Each row is retrieved by calling the function passed in the + * argument list. + * + * @param set The result set to stream + * @param dcb The connection to stream the result set to + */ +void +resultset_stream_json(RESULTSET *set, DCB *dcb) +{ +RESULT_COLUMN *col; +RESULT_ROW *row; +int rowno = 0; + + + dcb_printf(dcb, "[ "); + while ((row = (*set->fetchrow)(set, set->userdata)) != NULL) + { + int i = 0; + if (rowno++ > 0) + dcb_printf(dcb, ",\n"); + dcb_printf(dcb, "{ "); + col = set->column; + while (col) + { + + dcb_printf(dcb, "\"%s\" : ", col->name); + if (row->cols[i] && value_is_numeric(row->cols[i])) + dcb_printf(dcb, "%s", row->cols[i]); + else if (row->cols[i]) + dcb_printf(dcb, "\"%s\"", row->cols[i]); + else + dcb_printf(dcb, "NULL"); + i++; + col = col->next; + if (col) + dcb_printf(dcb, ", "); + } + resultset_free_row(row); + dcb_printf(dcb, "}"); + } + dcb_printf(dcb, "]\n"); +} diff --git a/server/core/server.c b/server/core/server.c index 0f39c0edc..425089d93 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -659,3 +659,75 @@ SERVER_PARAM *param = server->parameters; } return NULL; } + +/** + * Provide a row to the result set that defines the set of servers + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +serverRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +int i = 0;; +char *stat, buf[20]; +RESULT_ROW *row; +SERVER *ptr; + + spinlock_acquire(&server_spin); + ptr = allServers; + while (i < *rowno && ptr) + { + i++; + ptr = ptr->next; + } + if (ptr == NULL) + { + spinlock_release(&server_spin); + free(data); + return NULL; + } + (*rowno)++; + row = resultset_make_row(set); + resultset_row_set(row, 0, ptr->unique_name); + resultset_row_set(row, 1, ptr->name); + sprintf(buf, "%d", ptr->port); + resultset_row_set(row, 2, buf); + sprintf(buf, "%d", ptr->stats.n_current); + resultset_row_set(row, 3, buf); + stat = server_status(ptr); + resultset_row_set(row, 4, stat); + free(stat); + spinlock_release(&server_spin); + return row; +} + +/** + * Return a resultset that has the current set of servers in it + * + * @return A Result set + */ +RESULTSET * +serverGetList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(serverRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Server", 20, COL_TYPE_VARCHAR); + resultset_add_column(set, "Address", 15, COL_TYPE_VARCHAR); + resultset_add_column(set, "Port", 5, COL_TYPE_VARCHAR); + resultset_add_column(set, "Connections", 8, COL_TYPE_VARCHAR); + resultset_add_column(set, "Status", 20, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/service.c b/server/core/service.c index b364734e7..1d601c44a 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -34,6 +34,7 @@ * 09/09/14 Massimiliano Pinto Added service option for localhost authentication * 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services) * 06/02/15 Mark Riddoch Added caching of authentication data + * 18/02/15 Mark Riddoch Added result set management * * @endverbatim */ @@ -58,6 +59,8 @@ #include #include #include +#include + /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -1573,3 +1576,179 @@ void service_shutdown() } spinlock_release(&service_spin); } + +/** + * Return the count of all sessions active for all services + * + * @return Count of all active sessions + */ +int +serviceSessionCountAll() +{ +SERVICE *ptr; +int rval = 0; + + spinlock_acquire(&service_spin); + ptr = allServices; + while (ptr) + { + rval += ptr->stats.n_current; + ptr = ptr->next; + } + spinlock_release(&service_spin); + return rval; +} + +/** + * Provide a row to the result set that defines the set of service + * listeners + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +serviceListenerRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +int i = 0;; +char buf[20]; +RESULT_ROW *row; +SERVICE *ptr; +SERV_PROTOCOL *lptr = NULL; + + spinlock_acquire(&service_spin); + ptr = allServices; + if (ptr) + lptr = ptr->ports; + while (i < *rowno && ptr) + { + lptr = ptr->ports; + while (i < *rowno && lptr) + { + if ((lptr = lptr->next) != NULL) + i++; + } + if (i < *rowno) + { + ptr = ptr->next; + if (ptr && (lptr = ptr->ports) != NULL) + i++; + } + } + if (lptr == NULL) + { + spinlock_release(&service_spin); + free(data); + return NULL; + } + (*rowno)++; + row = resultset_make_row(set); + resultset_row_set(row, 0, ptr->name); + resultset_row_set(row, 1, lptr->protocol); + resultset_row_set(row, 2, (lptr && lptr->address) ? lptr->address : "*"); + sprintf(buf, "%d", lptr->port); + resultset_row_set(row, 3, buf); + resultset_row_set(row, 4, + (!lptr->listener || !lptr->listener->session || + lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ? + "Stopped" : "Running"); + spinlock_release(&service_spin); + return row; +} + +/** + * Return a resultset that has the current set of services in it + * + * @return A Result set + */ +RESULTSET * +serviceGetListenerList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(serviceListenerRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR); + resultset_add_column(set, "Protocol Module", 20, COL_TYPE_VARCHAR); + resultset_add_column(set, "Address", 15, COL_TYPE_VARCHAR); + resultset_add_column(set, "Port", 5, COL_TYPE_VARCHAR); + resultset_add_column(set, "State", 8, COL_TYPE_VARCHAR); + + return set; +} + +/** + * Provide a row to the result set that defines the set of services + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +serviceRowCallback(RESULTSET *set, void *data) +{ +int *rowno = (int *)data; +int i = 0;; +char buf[20]; +RESULT_ROW *row; +SERVICE *ptr; + + spinlock_acquire(&service_spin); + ptr = allServices; + while (i < *rowno && ptr) + { + i++; + ptr = ptr->next; + } + if (ptr == NULL) + { + spinlock_release(&service_spin); + free(data); + return NULL; + } + (*rowno)++; + row = resultset_make_row(set); + resultset_row_set(row, 0, ptr->name); + resultset_row_set(row, 1, ptr->routerModule); + sprintf(buf, "%d", ptr->stats.n_current); + resultset_row_set(row, 2, buf); + sprintf(buf, "%d", ptr->stats.n_sessions); + resultset_row_set(row, 3, buf); + spinlock_release(&service_spin); + return row; +} + +/** + * Return a resultset that has the current set of services in it + * + * @return A Result set + */ +RESULTSET * +serviceGetList() +{ +RESULTSET *set; +int *data; + + if ((data = (int *)malloc(sizeof(int))) == NULL) + return NULL; + *data = 0; + if ((set = resultset_create(serviceRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR); + resultset_add_column(set, "Router Module", 20, COL_TYPE_VARCHAR); + resultset_add_column(set, "No. Sessions", 10, COL_TYPE_VARCHAR); + resultset_add_column(set, "Total Sessions", 10, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/session.c b/server/core/session.c index f5fe2770c..3da8077b4 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -941,4 +941,102 @@ void session_close_timeouts(void* data) spinlock_release(&session_spin); } -} \ No newline at end of file +} + +/** + * Callback structure for the session list extraction + */ +typedef struct { + int index; + SESSIONLISTFILTER filter; +} SESSIONFILTER; + +/** + * Provide a row to the result set that defines the set of sessions + * + * @param set The result set + * @param data The index of the row to send + * @return The next row or NULL + */ +static RESULT_ROW * +sessionRowCallback(RESULTSET *set, void *data) +{ +SESSIONFILTER *cbdata = (SESSIONFILTER *)data; +int i = 0; +char buf[20]; +RESULT_ROW *row; +SESSION *ptr; + + spinlock_acquire(&session_spin); + ptr = allSessions; + /* Skip to the first non-listener if not showing listeners */ + while (ptr && cbdata->filter == SESSION_LIST_CONNECTION && + ptr->state == SESSION_STATE_LISTENER) + { + ptr = ptr->next; + } + while (i < cbdata->index && ptr) + { + if (cbdata->filter == SESSION_LIST_CONNECTION && + ptr->state != SESSION_STATE_LISTENER) + { + i++; + } + else if (cbdata->filter == SESSION_LIST_ALL) + { + i++; + } + ptr = ptr->next; + } + /* Skip to the next non-listener if not showing listeners */ + while (ptr && cbdata->filter == SESSION_LIST_CONNECTION && + ptr->state == SESSION_STATE_LISTENER) + { + ptr = ptr->next; + } + if (ptr == NULL) + { + spinlock_release(&session_spin); + free(data); + return NULL; + } + cbdata->index++; + row = resultset_make_row(set); + sprintf(buf, "%p", ptr); + resultset_row_set(row, 0, buf); + resultset_row_set(row, 1, ((ptr->client && ptr->client->remote) + ? ptr->client->remote : "")); + resultset_row_set(row, 2, (ptr->service && ptr->service->name + ? ptr->service->name : "")); + resultset_row_set(row, 3, session_state(ptr->state)); + spinlock_release(&session_spin); + return row; +} + +/** + * Return a resultset that has the current set of sessions in it + * + * @return A Result set + */ +RESULTSET * +sessionGetList(SESSIONLISTFILTER filter) +{ +RESULTSET *set; +SESSIONFILTER *data; + + if ((data = (SESSIONFILTER *)malloc(sizeof(SESSIONFILTER))) == NULL) + return NULL; + data->index = 0; + data->filter = filter; + if ((set = resultset_create(sessionRowCallback, data)) == NULL) + { + free(data); + return NULL; + } + resultset_add_column(set, "Session", 16, COL_TYPE_VARCHAR); + resultset_add_column(set, "Client", 15, COL_TYPE_VARCHAR); + resultset_add_column(set, "Service", 15, COL_TYPE_VARCHAR); + resultset_add_column(set, "State", 15, COL_TYPE_VARCHAR); + + return set; +} diff --git a/server/core/utils.c b/server/core/utils.c index da9378867..a26c2b4e5 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -42,6 +42,7 @@ #include #include #include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -235,3 +236,30 @@ int gw_getsockerrno( return_eno: return eno; } + +/** + * Create a HEX(SHA1(SHA1(password))) + * + * @param password The password to encrypt + * @return The new allocated encrypted password, that the caller must free + * + */ +char *create_hex_sha1_sha1_passwd(char *passwd) { + uint8_t hash1[SHA_DIGEST_LENGTH]=""; + uint8_t hash2[SHA_DIGEST_LENGTH]=""; + char *hexpasswd=NULL; + + if ((hexpasswd = (char *)calloc(SHA_DIGEST_LENGTH * 2 + 1, 1)) == NULL) + return NULL; + + /* hash1 is SHA1(real_password) */ + gw_sha1_str((uint8_t *)passwd, strlen(passwd), hash1); + + /* hash2 is the SHA1(input data), where input_data = SHA1(real_password) */ + gw_sha1_str(hash1, SHA_DIGEST_LENGTH, hash2); + + /* dbpass is the HEX form of SHA1(SHA1(real_password)) */ + gw_bin2hex(hexpasswd, hash2, SHA_DIGEST_LENGTH); + + return hexpasswd; +} diff --git a/server/include/config.h b/server/include/config.h index 93bd095c1..2cf9dccb3 100644 --- a/server/include/config.h +++ b/server/include/config.h @@ -1,5 +1,5 @@ -#ifndef _CONFIG_H -#define _CONFIG_H +#ifndef _MAXSCALE_CONFIG_H +#define _MAXSCALE_CONFIG_H /* * This file is distributed as part of the MariaDB Corporation MaxScale. It is free * software: you can redistribute it and/or modify it under the terms of the diff --git a/server/include/dcb.h b/server/include/dcb.h index 020b30e8b..b3efdac86 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -271,6 +271,18 @@ typedef struct dcb { #endif } DCB; +/** + * The DCB usage filer used for returning DCB's in use for a certain reason + */ +typedef enum { + DCB_USAGE_CLIENT, + DCB_USAGE_LISTENER, + DCB_USAGE_BACKEND, + DCB_USAGE_INTERNAL, + DCB_USAGE_ZOMBIE, + DCB_USAGE_ALL +} DCB_USAGE; + #if defined(FAKE_CODE) unsigned char dcb_fake_write_errno[10240]; __int32_t dcb_fake_write_ev[10240]; @@ -319,6 +331,7 @@ int dcb_add_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void *), void *); int dcb_isvalid(DCB *); /* Check the DCB is in the linked list */ +int dcb_count_by_usage(DCB_USAGE); /* Return counts of DCBs */ bool dcb_set_state(DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state); void dcb_call_foreach (struct server* server, DCB_REASON reason); diff --git a/server/include/modules.h b/server/include/modules.h index adda2b255..56215b5d2 100644 --- a/server/include/modules.h +++ b/server/include/modules.h @@ -19,6 +19,7 @@ */ #include #include +#include /** * @file modules.h Utilities for loading modules @@ -34,6 +35,7 @@ * 29/05/14 Mark Riddoch Addition of filter modules * 01/10/14 Mark Riddoch Addition of call to unload all modules on * shutdown + * 19/02/15 Mark Riddoch Addition of moduleGetList * @endverbatim */ @@ -63,6 +65,7 @@ extern void unload_module(const char *module); extern void unload_all_modules(); extern void printModules(); extern void dprintAllModules(DCB *); -char* get_maxscale_home(void); +extern char *get_maxscale_home(void); +extern RESULTSET *moduleGetList(); #endif diff --git a/server/include/modutil.h b/server/include/modutil.h index e1377e40b..b8e158c50 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -53,6 +53,7 @@ typedef struct rset_t{ }RESULTSET; extern int modutil_is_SQL(GWBUF *); +extern int modutil_is_SQL_prepare(GWBUF *); extern int modutil_extract_SQL(GWBUF *, char **, int *); extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *); extern char *modutil_get_SQL(GWBUF *); diff --git a/server/include/monitor.h b/server/include/monitor.h index c08e8153e..27d3f8144 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -19,6 +19,7 @@ */ #include #include +#include /** * @file monitor.h The interface to the monitor module @@ -35,6 +36,7 @@ * 28/08/14 Massimiliano Pinto Addition of detectStaleMaster * 30/10/14 Massimiliano Pinto Addition of disableMasterFailback * 07/11/14 Massimiliano Pinto Addition of setNetworkTimeout + * 19/02/15 Mark Riddoch Addition of monitorGetList * * @endverbatim */ @@ -143,4 +145,5 @@ extern void monitorSetReplicationHeartbeat(MONITOR *, int); extern void monitorDetectStaleMaster(MONITOR *, int); extern void monitorDisableMasterFailback(MONITOR *, int); extern void monitorSetNetworkTimeout(MONITOR *, int, int); +extern RESULTSET *monitorGetList(); #endif diff --git a/server/include/poll.h b/server/include/poll.h index 24bf0645d..e778c580c 100644 --- a/server/include/poll.h +++ b/server/include/poll.h @@ -19,6 +19,7 @@ */ #include #include +#include /** * @file poll.h The poll related functionality @@ -33,6 +34,22 @@ */ #define MAX_EVENTS 1000 +/** +* A statistic identifier that can be returned by poll_get_stat +*/ +typedef enum { + POLL_STAT_READ, + POLL_STAT_WRITE, + POLL_STAT_ERROR, + POLL_STAT_HANGUP, + POLL_STAT_ACCEPT, + POLL_STAT_EVQ_LEN, + POLL_STAT_EVQ_PENDING, + POLL_STAT_EVQ_MAX, + POLL_STAT_MAX_QTIME, + POLL_STAT_MAX_EXECTIME +} POLL_STAT; + extern void poll_init(); extern int poll_add_dcb(DCB *); extern int poll_remove_dcb(DCB *); @@ -46,4 +63,6 @@ extern void dShowThreads(DCB *dcb); void poll_add_epollin_event_to_dcb(DCB* dcb, GWBUF* buf); extern void dShowEventQ(DCB *dcb); extern void dShowEventStats(DCB *dcb); +extern int poll_get_stat(POLL_STAT stat); +extern RESULTSET *eventTimesGetList(); #endif diff --git a/server/include/resultset.h b/server/include/resultset.h new file mode 100644 index 000000000..bec76bb50 --- /dev/null +++ b/server/include/resultset.h @@ -0,0 +1,88 @@ +#ifndef _RESULTSET_H +#define _RESULTSET_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file resultset.h The MaxScale generic result set mechanism + * + * @verbatim + * Revision History + * + * Date Who Description + * 17/02/15 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include + + +/** + * Column types + */ +typedef enum { + COL_TYPE_VARCHAR = 0x0f, + COL_TYPE_VARSTRING = 0xfd +} RESULT_COL_TYPE; + +/** + * The result set column definition. Each result set has an order linked + * list of column definitions. + */ +typedef struct resultcolumn { + char *name; /*< Column name */ + int len; /*< Column length */ + RESULT_COL_TYPE type; /*< Column type */ + struct resultcolumn *next; /*< next column */ +} RESULT_COLUMN; + +/** + * A representation of a row within a result set. + */ +typedef struct resultrow { + int n_cols; /*< No. of columns in row */ + char **cols; /*< The columns themselves */ +} RESULT_ROW; + +struct resultset; + +/** + * Type of callback function used to supply each row + */ +typedef RESULT_ROW * (*RESULT_ROW_CB)(struct resultset *, void *); + +/** + * The representation of the result set itself. + */ +typedef struct resultset { + int n_cols; /*< No. of columns */ + RESULT_COLUMN *column; /*< Linked list of column definitions */ + RESULT_ROW_CB fetchrow; /*< Fetch a row for the result set */ + void *userdata; /*< User data for the fetch row call */ +} RESULTSET; + +extern RESULTSET *resultset_create(RESULT_ROW_CB, void *); +extern void resultset_free(RESULTSET *); +extern int resultset_add_column(RESULTSET *, char *, int, RESULT_COL_TYPE); +extern void resultset_column_free(RESULT_COLUMN *); +extern RESULT_ROW *resultset_make_row(RESULTSET *); +extern void resultset_free_row(RESULT_ROW *); +extern int resultset_row_set(RESULT_ROW *, int, char *); +extern void resultset_stream_mysql(RESULTSET *, DCB *); +extern void resultset_stream_json(RESULTSET *, DCB *); +#endif diff --git a/server/include/server.h b/server/include/server.h index 1eb2d3eeb..459869a0b 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -18,6 +18,7 @@ * Copyright MariaDB Corporation Ab 2013-2014 */ #include +#include /** * @file service.h @@ -41,6 +42,7 @@ * 30/07/14 Massimiliano Pinto Addition of NDB status for MySQL Cluster * 30/08/14 Massimiliano Pinto Addition of SERVER_STALE_STATUS * 27/10/14 Massimiliano Pinto Addition of SERVER_MASTER_STICKINESS + * 19/02/15 Mark Riddoch Addition of serverGetList * * @endverbatim */ @@ -187,4 +189,5 @@ extern void serverAddParameter(SERVER *, char *, char *); extern char *serverGetParameter(SERVER *, char *); extern void server_update(SERVER *, char *, char *, char *); extern void server_set_unique_name(SERVER *, char *); +extern RESULTSET *serverGetList(); #endif diff --git a/server/include/service.h b/server/include/service.h index 42685e460..6347b7b59 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "config.h" /** @@ -199,4 +200,7 @@ extern void dListServices(DCB *); extern void dListListeners(DCB *); char* service_get_name(SERVICE* svc); void service_shutdown(); +extern int serviceSessionCountAll(); +extern RESULTSET *serviceGetList(); +extern RESULTSET *serviceGetListenerList(); #endif diff --git a/server/include/session.h b/server/include/session.h index fd76eb2b1..908277e41 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -100,6 +101,14 @@ typedef struct { void *session; } SESSION_FILTER; +/** + * Filter type for the sessionGetList call + */ +typedef enum { + SESSION_LIST_ALL, + SESSION_LIST_CONNECTION +} SESSIONLISTFILTER; + /** * The session status block * @@ -169,5 +178,6 @@ SESSION* get_session_by_router_ses(void* rses); void session_enable_log(SESSION* ses, logfile_id_t id); void session_disable_log(SESSION* ses, logfile_id_t id); void session_close_timeouts(void* data); +RESULTSET *sessionGetList(SESSIONLISTFILTER); #endif diff --git a/server/modules/filter/CMakeLists.txt b/server/modules/filter/CMakeLists.txt index 87520abc7..98d855d54 100644 --- a/server/modules/filter/CMakeLists.txt +++ b/server/modules/filter/CMakeLists.txt @@ -34,6 +34,10 @@ add_library(namedserverfilter SHARED namedserverfilter.c) target_link_libraries(namedserverfilter log_manager utils) install(TARGETS namedserverfilter DESTINATION modules) +add_library(lagfilter SHARED lagfilter.c) +target_link_libraries(lagfilter log_manager utils query_classifier) +install(TARGETS lagfilter DESTINATION modules) + add_subdirectory(hint) diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index 8fbfc9435..d33cacb2e 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -1300,6 +1300,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue char *ptr,*where,*msg = NULL; char emsg[512]; int qlen; + unsigned char* memptr = (unsigned char*)queue->start; bool is_sql, is_real, matches; skygw_query_op_t optype = QUERY_OP_UNDEFINED; STRLINK* strln = NULL; @@ -1312,15 +1313,15 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue tm_now = localtime(&time_now); matches = false; - is_sql = modutil_is_SQL(queue); + is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue); if(is_sql){ if(!query_is_parsed(queue)){ parse_query(queue); } optype = query_classifier_get_operation(queue); - modutil_extract_SQL(queue, &ptr, &qlen); is_real = skygw_is_real_query(queue); + qlen = gw_mysql_get_byte3(memptr) - 1; } if(rulelist->rule->on_queries == QUERY_OP_UNDEFINED || rulelist->rule->on_queries & optype){ @@ -1547,18 +1548,19 @@ bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu bool is_sql, rval = false; int qlen; char *fullquery = NULL,*ptr; - + unsigned char* memptr = (unsigned char*)queue->start; RULELIST* rulelist; - is_sql = modutil_is_SQL(queue); + is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue); if(is_sql){ if(!query_is_parsed(queue)){ parse_query(queue); } - modutil_extract_SQL(queue, &ptr, &qlen); - fullquery = malloc((qlen + 1) * sizeof(char)); - memcpy(fullquery,ptr,qlen); - memset(fullquery + qlen,0,1); + + qlen = gw_mysql_get_byte3(memptr); + fullquery = malloc((qlen) * sizeof(char)); + memcpy(fullquery,memptr + 5,qlen - 1); + memset(fullquery + qlen - 1,0,1); } if((rulelist = user->rules_or) == NULL) @@ -1598,21 +1600,21 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *qu { bool is_sql, rval = true; int qlen; + unsigned char* memptr = (unsigned char*)queue->start; char *fullquery = NULL,*ptr; RULELIST* rulelist; - is_sql = modutil_is_SQL(queue); - + is_sql = modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue); + if(is_sql){ if(!query_is_parsed(queue)){ parse_query(queue); } - modutil_extract_SQL(queue, &ptr, &qlen); - fullquery = malloc((qlen + 1) * sizeof(char)); - memcpy(fullquery,ptr,qlen); - memset(fullquery + qlen,0,1); - + qlen = gw_mysql_get_byte3(memptr); + fullquery = malloc((qlen) * sizeof(char)); + memcpy(fullquery,memptr + 5,qlen - 1); + memset(fullquery + qlen - 1,0,1); } if(strict_all) diff --git a/server/modules/filter/lagfilter.c b/server/modules/filter/lagfilter.c new file mode 100644 index 000000000..aff236850 --- /dev/null +++ b/server/modules/filter/lagfilter.c @@ -0,0 +1,400 @@ +/* + * This file is distributed as part of MaxScale by MariaDB Corporation. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Defined in log_manager.cc */ +extern int lm_enabled_logfiles_bitmask; +extern size_t log_ses_count[]; +extern __thread log_info_t tls_log_info; + +/** + * @file lagfilter.c - a very simple filter designed to send queries to the + * master server after data modification has occurred. This is done to prevent + * replication lag affecting the outcome of a select query. + * + * @verbatim + * + * Two optional parameters that define the behavior after a data modifying query + * is executed: + * + * count= Queries to route to master after data modification. + * time=