Merge branch '2.3' into 2.4

This commit is contained in:
Markus Mäkelä
2019-10-10 21:42:43 +03:00
12 changed files with 854 additions and 231 deletions

View File

@ -64,4 +64,5 @@ private:
std::vector<std::vector<std::string>> m_rows;
ResultSet(std::initializer_list<std::string> names);
json_t* get_json_value(const std::string& s);
};

View File

@ -6,7 +6,10 @@ if (BUILD_MAXCTRL)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lib/version.js.in ${CMAKE_CURRENT_BINARY_DIR}/lib/version.js @ONLY)
file(GLOB_RECURSE MAXCTRL_SOURCES lib/*)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/maxctrl/maxctrl
DEPENDS ${MAXCTRL_SOURCES} maxctrl.js
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.sh ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
add_custom_target(maxctrl ALL DEPENDS ${CMAKE_BINARY_DIR}/maxctrl/maxctrl)

View File

@ -112,7 +112,7 @@ module.exports = function() {
row = []
fields.forEach(function(p) {
var v = _.getPath(i, p[Object.keys(p)[0]], '')
var v = _.getPath(i, p.path, '')
if (Array.isArray(v)) {
v = v.join(', ')
@ -162,7 +162,7 @@ module.exports = function() {
var header = []
fields.forEach(function(i) {
header.push(Object.keys(i))
header.push(i.name)
})
var table = getTable(header)
@ -188,7 +188,7 @@ module.exports = function() {
var header = []
fields.forEach(function(i) {
header.push(Object.keys(i))
header.push(i.name)
})
var table = getTable(header)
@ -197,7 +197,7 @@ module.exports = function() {
row = []
fields.forEach(function(p) {
var v = _.getPath(i, p[Object.keys(p)[0]], '')
var v = _.getPath(i, p[p.name], '')
if (Array.isArray(v) && typeof(v[0]) != 'object') {
v = v.join(', ')
@ -229,7 +229,7 @@ module.exports = function() {
separator = '\n'
var max_field_length = 0
fields.forEach(function (i) {
var k = Object.keys(i)[0]
var k = i.name
if (k.length > max_field_length) {
max_field_length = k.length
}
@ -244,8 +244,8 @@ module.exports = function() {
}
fields.forEach(function(i) {
var k = Object.keys(i)[0]
var path = i[k]
var k = i.name
var path = i.path
var v = _.getPath(data, path, '')
if (Array.isArray(v) && typeof(v[0]) != 'object') {
@ -422,6 +422,24 @@ module.exports = function() {
default: false
}
}
this.fieldDescriptions = function(fields) {
var t = new Table({chars: {
'top' : '', 'top-mid': '', 'top-left': '', 'top-right': '', 'left': ' ', 'right': '',
'left-mid': '' , 'mid': '' , 'mid-mid': '', 'right-mid': '' , 'middle': '',
'bottom' : '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
}})
t.push(['Field', 'Description'])
t.push(['-----', '-----------'])
for (f of fields) {
t.push([f.name, f.description])
}
return '\n\n' + t.toString()
}
}
@ -441,8 +459,6 @@ var tsvopts = {
'padding-right': 0,
compact: true
},
}
function getList() {

View File

@ -13,24 +13,252 @@
require('./common.js')()
const list_servers_fields = [
{
name: 'Server',
path: 'id',
description: 'Server name'
},
{
name: 'Address',
path: 'attributes.parameters.address',
description: 'Address where the server listens'
},
{
name: 'Port',
path: 'attributes.parameters.port',
description: 'The port on which the server listens'
},
{
name: 'Connections',
path: 'attributes.statistics.connections',
description: 'Current connection count'
},
{
name: 'State',
path: 'attributes.state',
description: 'Server state'
},
{
name: 'GTID',
path: 'attributes.gtid_current_pos',
description: 'Current value of @@gtid_current_pos'
}
]
const list_services_fields = [
{
name: 'Service',
path: 'id',
description: 'Service name'
},
{
name: 'Router',
path: 'attributes.router',
description: 'Router used by the service'
},
{
name: 'Connections',
path: 'attributes.connections',
description: 'Current connection count'
},
{
name: 'Total Connections',
path: 'attributes.total_connections',
description: 'Total connection count'
},
{
name: 'Servers',
path: 'relationships.servers.data[].id',
description: 'Servers that the service uses'
}
]
const list_listeners_fields = [
{
name: 'Name',
path: 'id',
description: 'Listener name'
},
{
name: 'Port',
path: 'attributes.parameters.port',
description: 'The port where the listener listens'
},
{
name: 'Host',
path: 'attributes.parameters.host',
description: 'The address or socket where the listener listens'
},
{
name: 'State',
path: 'attributes.state',
description: 'Listener state'
}
]
const list_monitors_fields = [
{
name: 'Monitor',
path: 'id',
description: 'Monitor name'
},
{
name: 'State',
path: 'attributes.state',
description: 'Monitor state'
},
{
name: 'Servers',
path: 'relationships.servers.data[].id',
description: 'The servers that this monitor monitors'
}
]
const list_sessions_fields = [
{
name: 'Id',
path: 'id',
description: 'Session ID'
},
{
name: 'User',
path: 'attributes.user',
description: 'Username'
},
{
name: 'Host',
path: 'attributes.remote',
description: 'Client host address'
},
{
name: 'Connected',
path: 'attributes.connected',
description: 'Time when the session started'
},
{
name: 'Idle',
path: 'attributes.idle',
description: 'How long the session has been idle, in seconds'
},
{
name: 'Service',
path: 'relationships.services.data[].id',
description: 'The service where the session connected'
}
]
const list_filters_fields = [
{
name: 'Filter',
path: 'id',
description: 'Filter name'
},
{
name: 'Service',
path: 'relationships.services.data[].id',
description: 'Services that use the filter'
},
{
name: 'Module',
path: 'attributes.module',
description: 'The module that the filter uses'
}
]
const list_modules_fields = [
{
name: 'Module',
path: 'id',
description: 'Module name'
},
{
name: 'Type',
path: 'attributes.module_type',
description: 'Module type'
},
{
name: 'Version',
path: 'attributes.version',
description: 'Module version'
}
]
const list_threads_fields = [
{
name: 'Id',
path: 'id',
description: 'Thread ID'
},
{
name: 'Current FDs',
path: 'attributes.stats.current_descriptors',
description: 'Current number of managed file descriptors'
},
{
name: 'Total FDs',
path: 'attributes.stats.total_descriptors',
description: 'Total number of managed file descriptors'
},
{
name: 'Load (1s)',
path: 'attributes.stats.load.last_second',
description: 'Load percentage over the last second'
},
{
name: 'Load (1m)',
path: 'attributes.stats.load.last_minute',
description: 'Load percentage over the last minute'
},
{
name: 'Load (1h)',
path: 'attributes.stats.load.last_hour',
description: 'Load percentage over the last hour'
}
]
const list_users_fields = [
{
name: 'Name',
path: 'id',
description: 'User name'
},
{
name: 'Type',
path: 'type',
description: 'User type'
},
{
name: 'Privileges',
path: 'attributes.account',
description: 'User privileges'
},
]
const list_commands_fields = [
{
name: 'Module',
path: 'id',
description: 'Module name'
},
{
name: 'Commands',
path: 'attributes.commands[].id',
description: 'Available commands'
}
]
exports.command = 'list <command>'
exports.desc = 'List objects'
exports.handler = function() {}
exports.builder = function(yargs) {
yargs
.command('servers', 'List servers', function(yargs) {
return yargs.epilog('List all servers in MaxScale.')
return yargs.epilog('List all servers in MaxScale.' +
fieldDescriptions(list_servers_fields))
.usage('Usage: list servers')
}, function(argv) {
maxctrl(argv, function(host) {
fields = [
{'Server': 'id'},
{'Address': 'attributes.parameters.address'},
{'Port': 'attributes.parameters.port'},
{'Connections': 'attributes.statistics.connections'},
{'State': 'attributes.state'},
{'GTID': 'attributes.gtid_current_pos'}
]
// First, get the list of all servers
return getJson(host, 'servers')
@ -76,52 +304,41 @@ exports.builder = function(yargs) {
}
})
})
.then(() => filterResource(res, fields))
.then((res) => rawCollectionAsTable(res, fields))
.then(() => filterResource(res, list_servers_fields))
.then((res) => rawCollectionAsTable(res, list_servers_fields))
})
})
})
.command('services', 'List services', function(yargs) {
return yargs.epilog('List all services and the servers they use.')
return yargs.epilog('List all services and the servers they use.' +
fieldDescriptions(list_services_fields))
.usage('Usage: list services')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'services',[
{'Service': 'id'},
{'Router': 'attributes.router'},
{'Connections': 'attributes.connections'},
{'Total Connections': 'attributes.total_connections'},
{'Servers': 'relationships.servers.data[].id'}
])
return getCollection(host, 'services', list_services_fields)
})
})
.command('listeners <service>', 'List listeners of a service', function(yargs) {
return yargs.epilog('List listeners for a service.')
return yargs.epilog('List listeners for a service.' +
fieldDescriptions(list_listeners_fields))
.usage('Usage: list listeners <service>')
}, function(argv) {
maxctrl(argv, function(host) {
return getSubCollection(host, 'services/' + argv.service, 'attributes.listeners', [
{'Name': 'id'},
{'Port': 'attributes.parameters.port'},
{'Host': 'attributes.parameters.host'},
{'State': 'attributes.state'}
])
return getSubCollection(host, 'services/' + argv.service, 'attributes.listeners', list_listeners_fields)
})
})
.command('monitors', 'List monitors', function(yargs) {
return yargs.epilog('List all monitors in MaxScale.')
return yargs.epilog('List all monitors in MaxScale.' +
fieldDescriptions(list_monitors_fields))
.usage('Usage: list monitors')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'monitors', [
{'Monitor': 'id'},
{'State': 'attributes.state'},
{'Servers': 'relationships.servers.data[].id'}
])
return getCollection(host, 'monitors', list_monitors_fields)
})
})
.command('sessions', 'List sessions', function(yargs) {
return yargs.epilog('List all client sessions.')
return yargs.epilog('List all client sessions.' +
fieldDescriptions(list_sessions_fields))
.usage('Usage: list sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
@ -131,76 +348,53 @@ exports.builder = function(yargs) {
if (argv[this.rDnsOption.shortname]) {
resource += '?' + this.rDnsOption.optionOn
}
return getCollection(host, resource,[
{'Id': 'id'},
{'User': 'attributes.user'},
{'Host': 'attributes.remote'},
{'Connected': 'attributes.connected'},
{'Idle': 'attributes.idle'},
{'Service': 'relationships.services.data[].id'}
])
return getCollection(host, 'sessions', list_sessions_fields)
})
})
.command('filters', 'List filters', function(yargs) {
return yargs.epilog('List all filters in MaxScale.')
return yargs.epilog('List all filters in MaxScale.' +
fieldDescriptions(list_filters_fields))
.usage('Usage: list filters')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'filters', [
{'Filter': 'id'},
{'Service': 'relationships.services.data[].id'},
{'Module': 'attributes.module'}
])
return getCollection(host, 'filters', list_filters_fields)
})
})
.command('modules', 'List loaded modules', function(yargs) {
return yargs.epilog('List all currently loaded modules.')
return yargs.epilog('List all currently loaded modules.' +
fieldDescriptions(list_modules_fields))
.usage('Usage: list modules')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'maxscale/modules',[
{'Module':'id'},
{'Type':'attributes.module_type'},
{'Version': 'attributes.version'}
])
return getCollection(host, 'maxscale/modules', list_modules_fields)
})
})
.command('threads', 'List threads', function(yargs) {
return yargs.epilog('List all worker threads.')
return yargs.epilog('List all worker threads.' +
fieldDescriptions(list_threads_fields))
.usage('Usage: list threads')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'maxscale/threads', [
{'Id': 'id'},
{'Current FDs': 'attributes.stats.current_descriptors'},
{'Total FDs': 'attributes.stats.total_descriptors'},
{'Load (1s)': 'attributes.stats.load.last_second'},
{'Load (1m)': 'attributes.stats.load.last_minute'},
{'Load (1h)': 'attributes.stats.load.last_hour'}
])
return getCollection(host, 'maxscale/threads', list_threads_fields)
})
})
.command('users', 'List created users', function(yargs) {
return yargs.epilog('List network the users that can be used to connect to the MaxScale REST API as well as enabled local accounts.')
return yargs.epilog('List network the users that can be used to connect to the MaxScale REST API' +
' as well as enabled local accounts.' +
fieldDescriptions(list_users_fields))
.usage('Usage: list users')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'users',[
{'Name':'id'},
{'Type':'type'},
{'Privileges':'attributes.account'},
])
return getCollection(host, 'users', list_users_fields)
})
})
.command('commands', 'List module commands', function(yargs) {
return yargs.epilog('List all available module commands.')
return yargs.epilog('List all available module commands.' +
fieldDescriptions(list_commands_fields))
.usage('Usage: list commands')
}, function(argv) {
maxctrl(argv, function(host) {
return getCollection(host, 'maxscale/modules',[
{'Module':'id'},
{'Commands': 'attributes.commands[].id'}
])
return getCollection(host, 'maxscale/modules', list_commands_fields)
})
})
.usage('Usage: list <command>')

View File

@ -14,94 +14,438 @@
require('./common.js')()
const server_fields = [
{'Server': 'id'},
{'Address': 'attributes.parameters.address'},
{'Port': 'attributes.parameters.port'},
{'State': 'attributes.state'},
{'Last Event': 'attributes.last_event'},
{'Triggered At': 'attributes.triggered_at'},
{'Services': 'relationships.services.data[].id'},
{'Monitors': 'relationships.monitors.data[].id'},
{'Master ID': 'attributes.master_id'},
{'Node ID': 'attributes.node_id'},
{'Slave Server IDs': 'attributes.slaves'},
{'Statistics': 'attributes.statistics'},
{'Parameters': 'attributes.parameters'}
{
name: 'Server',
path: 'id',
description: 'Server name'
},
{
name: 'Address',
path: 'attributes.parameters.address',
description: 'Address where the server listens'
},
{
name: 'Port',
path: 'attributes.parameters.port',
description: 'The port on which the server listens'
},
{
name: 'State',
path: 'attributes.state',
description: 'Server state'
},
{
name: 'Last Event',
path: 'attributes.last_event',
description: 'The type of the latest event'
},
{
name: 'Triggered At',
path: 'attributes.triggered_at',
description: 'Time when the latest event was triggered at'
},
{
name: 'Services',
path: 'relationships.services.data[].id',
description: 'Services that use this server'
},
{
name: 'Monitors',
path: 'relationships.monitors.data[].id',
description: 'Monitors that monitor this server'
},
{
name: 'Master ID',
path: 'attributes.master_id',
description: 'The server ID of the master'
},
{
name: 'Node ID',
path: 'attributes.node_id',
description: 'The node ID of this server'
},
{
name: 'Slave Server IDs',
path: 'attributes.slaves',
description: 'List of slave server IDs'
},
{
name: 'Statistics',
path: 'attributes.statistics',
description: 'Server statistics'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Server parameters'
}
]
const service_fields = [
{'Service': 'id'},
{'Router': 'attributes.router'},
{'State': 'attributes.state'},
{'Started At': 'attributes.started'},
{'Current Connections': 'attributes.connections'},
{'Total Connections': 'attributes.total_connections'},
{'Servers': 'relationships.servers.data[].id'},
{'Parameters': 'attributes.parameters'},
{'Router Diagnostics': 'attributes.router_diagnostics'}
{
name: 'Service',
path: 'id',
description: 'Service name'
},
{
name: 'Router',
path: 'attributes.router',
description: 'Router that the service uses'
},
{
name: 'State',
path: 'attributes.state',
description: 'Service state'
},
{
name: 'Started At',
path: 'attributes.started',
description: 'When the service was started'
},
{
name: 'Current Connections',
path: 'attributes.connections',
description: 'Current connection count'
},
{
name: 'Total Connections',
path: 'attributes.total_connections',
description: 'Total connection count'
},
{
name: 'Servers',
path: 'relationships.servers.data[].id',
description: 'Servers that the service uses'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Service parameter'
},
{
name: 'Router Diagnostics',
path: 'attributes.router_diagnostics',
description: 'Diagnostics provided by the router module'
}
]
const monitor_fields = [
{'Monitor': 'id'},
{'State': 'attributes.state'},
{'Servers': 'relationships.servers.data[].id'},
{'Parameters': 'attributes.parameters'},
{'Monitor Diagnostics': 'attributes.monitor_diagnostics'}
{
name: 'Monitor',
path: 'id',
description: 'Monitor name'
},
{
name: 'State',
path: 'attributes.state',
description: 'Monitor state'
},
{
name: 'Servers',
path: 'relationships.servers.data[].id',
description: 'The servers that this monitor monitors'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Monitor parameters'
},
{
name: 'Monitor Diagnostics',
path: 'attributes.monitor_diagnostics',
description: 'Diagnostics provided by the monitor module'
}
]
const session_fields = [
{'Id': 'id'},
{'Service': 'relationships.services.data[].id'},
{'State': 'attributes.state'},
{'User': 'attributes.user'},
{'Host': 'attributes.remote'},
{'Connected': 'attributes.connected'},
{'Idle': 'attributes.idle'},
{'Connections': 'attributes.connections[].server'},
{'Connection IDs': 'attributes.connections[].protocol_diagnostics.connection_id'},
{'Queries': 'attributes.queries[].statement'},
{'Log': 'attributes.log'}
{
name: 'Id',
path: 'id',
description: 'Session ID'
},
{
name: 'Service',
path: 'relationships.services.data[].id',
description: 'The service where the session connected'
},
{
name: 'State',
path: 'attributes.state',
description: 'Session state'
},
{
name: 'User',
path: 'attributes.user',
description: 'Username'
},
{
name: 'Host',
path: 'attributes.remote',
description: 'Client host address'
},
{
name: 'Connected',
path: 'attributes.connected',
description: 'Time when the session started'
},
{
name: 'Idle',
path: 'attributes.idle',
description: 'How long the session has been idle, in seconds'
},
{
name: 'Connections',
path: 'attributes.connections[].server',
description: 'Ordered list of backend connections'
},
{
name: 'Connection IDs',
path: 'attributes.connections[].protocol_diagnostics.connection_id',
description: 'Thread IDs for the backend connections'
},
{
name: 'Queries',
path: 'attributes.queries[].statement',
description: 'Query history'
},
{
name: 'Log',
path: 'attributes.log',
description: 'Per-session log messages'
}
]
const filter_fields = [
{'Filter': 'id'},
{'Module': 'attributes.module'},
{'Services': 'relationships.services.data[].id'},
{'Parameters': 'attributes.parameters'}
{
name: 'Filter',
path: 'id',
description: 'Filter name'
},
{
name: 'Module',
path: 'attributes.module',
description: 'The module that the filter uses'
},
{
name: 'Services',
path: 'relationships.services.data[].id',
description: 'Services that use the filter'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Filter parameters'
}
]
const module_fields = [
{'Module': 'id'},
{'Type': 'attributes.module_type'},
{'Version': 'attributes.version'},
{'Maturity': 'attributes.maturity'},
{'Description': 'attributes.description'},
{'Parameters': 'attributes.parameters'},
{'Commands': 'attributes.commands'}
{
name: 'Module',
path: 'id',
description: 'Module name'
},
{
name: 'Type',
path: 'attributes.module_type',
description: 'Module type'
},
{
name: 'Version',
path: 'attributes.version',
description: 'Module version'
},
{
name: 'Maturity',
path: 'attributes.maturity',
description: 'Module maturity'
},
{
name: 'Description',
path: 'attributes.description',
description: 'Short description about the module'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'All the parameters that the module accepts'
},
{
name: 'Commands',
path: 'attributes.commands',
description: 'Commands that the module provides'
}
]
const thread_fields = [
{'Id': 'id'},
{'Accepts': 'attributes.stats.accepts'},
{'Reads': 'attributes.stats.reads'},
{'Writes': 'attributes.stats.writes'},
{'Hangups': 'attributes.stats.hangups'},
{'Errors': 'attributes.stats.errors'},
{'Blocking polls': 'attributes.stats.blocking_polls'},
{'Avg event queue length': 'attributes.stats.avg_event_queue_length'},
{'Max event queue length': 'attributes.stats.max_event_queue_length'},
{'Max exec time': 'attributes.stats.max_exec_time'},
{'Max queue time': 'attributes.stats.max_queue_time'},
{'Current FDs': 'attributes.stats.current_descriptors'},
{'Total FDs': 'attributes.stats.total_descriptors'},
{'Load (1s)': 'attributes.stats.load.last_second'},
{'Load (1m)': 'attributes.stats.load.last_minute'},
{'Load (1h)': 'attributes.stats.load.last_hour'},
{'QC cache size': 'attributes.stats.query_classifier_cache.size'},
{'QC cache inserts': 'attributes.stats.query_classifier_cache.inserts'},
{'QC cache hits': 'attributes.stats.query_classifier_cache.hits'},
{'QC cache misses': 'attributes.stats.query_classifier_cache.misses'},
{'QC cache evictions': 'attributes.stats.query_classifier_cache.evictions'},
{
name: 'Id',
path: 'id',
description: 'Thread ID'
},
{
name: 'Accepts',
path: 'attributes.stats.accepts',
description: 'Number of TCP accepts done by this thread'
},
{
name: 'Reads',
path: 'attributes.stats.reads',
description: 'Number of EPOLLIN events'
},
{
name: 'Writes',
path: 'attributes.stats.writes',
description: 'Number of EPOLLOUT events'
},
{
name: 'Hangups',
path: 'attributes.stats.hangups',
description: 'Number of EPOLLHUP and EPOLLRDUP events'
},
{
name: 'Errors',
path: 'attributes.stats.errors',
description: 'Number of EPOLLERR events'
},
{
name: 'Avg event queue length',
path: 'attributes.stats.avg_event_queue_length',
description: 'Average number of events returned by one epoll_wait call'
},
{
name: 'Max event queue length',
path: 'attributes.stats.max_event_queue_length',
description: 'Maximum number of events returned by one epoll_wait call'
},
{
name: 'Max exec time',
path: 'attributes.stats.max_exec_time',
description: 'The longest time spent processing events returned by a epoll_wait call'
},
{
name: 'Max queue time',
path: 'attributes.stats.max_queue_time',
description: 'The longest time an event had to wait before it was processed'
},
{
name: 'Current FDs',
path: 'attributes.stats.current_descriptors',
description: 'Current number of managed file descriptors'
},
{
name: 'Total FDs',
path: 'attributes.stats.total_descriptors',
description: 'Total number of managed file descriptors'
},
{
name: 'Load (1s)',
path: 'attributes.stats.load.last_second',
description: 'Load percentage over the last second'
},
{
name: 'Load (1m)',
path: 'attributes.stats.load.last_minute',
description: 'Load percentage over the last minute'
},
{
name: 'Load (1h)',
path: 'attributes.stats.load.last_hour',
description: 'Load percentage over the last hour'
},
{
name: 'QC cache size',
path: 'attributes.stats.query_classifier_cache.size',
description: 'Query classifier size'
},
{
name: 'QC cache inserts',
path: 'attributes.stats.query_classifier_cache.inserts',
description: 'Number of times a new query was added into the query classification cache'
},
{
name: 'QC cache hits',
path: 'attributes.stats.query_classifier_cache.hits',
description: 'How many times a query classification was found in the query classification cache'
},
{
name: 'QC cache misses',
path: 'attributes.stats.query_classifier_cache.misses',
description: 'How many times a query classification was not found in the query classification cache'
},
{
name: 'QC cache evictions',
path: 'attributes.stats.query_classifier_cache.evictions',
description: 'How many times a query classification result was evicted from the query classification cache'
},
]
const show_maxscale_fields = [
{
name: 'Version',
path: 'attributes.version',
description: 'MaxScale version'
},
{
name: 'Commit',
path: 'attributes.commit',
description: 'MaxScale commit ID'
},
{
name: 'Started At',
path: 'attributes.started_at',
description: 'Time when MaxScale was started'
},
{
name: 'Activated At',
path: 'attributes.activated_at',
description: 'Time when MaxScale left passive mode'
},
{
name: 'Uptime',
path: 'attributes.uptime',
description: 'Time MaxScale has been running'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Global MaxScale parameters'
}
]
const show_logging_fields = [
{
name: 'Current Log File',
path: 'attributes.log_file',
description: 'The current log file MaxScale is logging into'
},
{
name: 'Enabled Log Levels',
path: 'attributes.log_priorities',
description: 'List of log levels enabled in MaxScale'
},
{
name: 'Parameters',
path: 'attributes.parameters',
description: 'Logging parameters'
}
]
const show_commands_fields = [
{
name: 'Command',
path: 'id',
description: 'Command name'
},
{
name: 'Parameters',
path: 'attributes.parameters[].type',
description: 'Parameters the command supports'
},
{
name: 'Descriptions',
path: 'attributes.parameters[].description',
description: 'Parameter descriptions'
}
]
exports.command = 'show <command>'
@ -113,7 +457,7 @@ exports.builder = function(yargs) {
return yargs.epilog('Show detailed information about a server. The `Parameters` ' +
'field contains the currently configured parameters for this ' +
'server. See `help alter server` for more details about altering ' +
'server parameters.')
'server parameters.' + fieldDescriptions(server_fields))
.usage('Usage: show server <server>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -121,7 +465,8 @@ exports.builder = function(yargs) {
})
})
.command('servers', 'Show all servers', function(yargs) {
return yargs.epilog('Show detailed information about all servers.')
return yargs.epilog('Show detailed information about all servers.' +
fieldDescriptions(server_fields))
.usage('Usage: show servers')
}, function(argv) {
maxctrl(argv, function(host) {
@ -132,7 +477,7 @@ exports.builder = function(yargs) {
return yargs.epilog('Show detailed information about a service. The `Parameters` ' +
'field contains the currently configured parameters for this ' +
'service. See `help alter service` for more details about altering ' +
'service parameters.')
'service parameters.' + fieldDescriptions(service_fields))
.usage('Usage: show service <service>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -140,7 +485,8 @@ exports.builder = function(yargs) {
})
})
.command('services', 'Show all services', function(yargs) {
return yargs.epilog('Show detailed information about all services.')
return yargs.epilog('Show detailed information about all services.' +
fieldDescriptions(service_fields))
.usage('Usage: show services')
}, function(argv) {
maxctrl(argv, function(host) {
@ -151,7 +497,7 @@ exports.builder = function(yargs) {
return yargs.epilog('Show detailed information about a monitor. The `Parameters` ' +
'field contains the currently configured parameters for this ' +
'monitor. See `help alter monitor` for more details about altering ' +
'monitor parameters.')
'monitor parameters.' + fieldDescriptions(monitor_fields))
.usage('Usage: show monitor <monitor>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -159,7 +505,8 @@ exports.builder = function(yargs) {
})
})
.command('monitors', 'Show all monitors', function(yargs) {
return yargs.epilog('Show detailed information about all monitors.')
return yargs.epilog('Show detailed information about all monitors.' +
fieldDescriptions(monitor_fields))
.usage('Usage: show monitors')
}, function(argv) {
maxctrl(argv, function(host) {
@ -173,7 +520,8 @@ exports.builder = function(yargs) {
'ID of a particular session.\n\n' +
'The `Connections` field lists the servers to which ' +
'the session is connected and the `Connection IDs` ' +
'field lists the IDs for those connections.')
'field lists the IDs for those connections.' +
fieldDescriptions(session_fields))
.usage('Usage: show session <session>')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
@ -188,7 +536,8 @@ exports.builder = function(yargs) {
})
.command('sessions', 'Show all sessions', function(yargs) {
return yargs.epilog('Show detailed information about all sessions. ' +
'See `help show session` for more details.')
'See `help show session` for more details.' +
fieldDescriptions(session_fields))
.usage('Usage: show sessions')
.group([rDnsOption.shortname], 'Options:')
.option(rDnsOption.shortname, rDnsOption.definition)
@ -202,7 +551,8 @@ exports.builder = function(yargs) {
})
})
.command('filter <filter>', 'Show filter', function(yargs) {
return yargs.epilog('The list of services that use this filter is show in the `Services` field.')
return yargs.epilog('The list of services that use this filter is show in the `Services` field.' +
fieldDescriptions(filter_fields))
.usage('Usage: show filter <filter>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -210,7 +560,8 @@ exports.builder = function(yargs) {
})
})
.command('filters', 'Show all filters', function(yargs) {
return yargs.epilog('Show detailed information of all filters.')
return yargs.epilog('Show detailed information of all filters.' +
fieldDescriptions(filter_fields))
.usage('Usage: show filters')
}, function(argv) {
maxctrl(argv, function(host) {
@ -219,7 +570,8 @@ exports.builder = function(yargs) {
})
.command('module <module>', 'Show loaded module', function(yargs) {
return yargs.epilog('This command shows all available parameters as well as ' +
'detailed version information of a loaded module.')
'detailed version information of a loaded module.' +
fieldDescriptions(module_fields))
.usage('Usage: show module <module>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -227,7 +579,8 @@ exports.builder = function(yargs) {
})
})
.command('modules', 'Show all loaded modules', function(yargs) {
return yargs.epilog('Displays detailed information about all modules.')
return yargs.epilog('Displays detailed information about all modules.' +
fieldDescriptions(module_fields))
.usage('Usage: show modules')
}, function(argv) {
maxctrl(argv, function(host) {
@ -236,22 +589,16 @@ exports.builder = function(yargs) {
})
.command('maxscale', 'Show MaxScale information', function(yargs) {
return yargs.epilog('See `help alter maxscale` for more details about altering ' +
'MaxScale parameters.')
'MaxScale parameters.' + fieldDescriptions(show_maxscale_fields))
.usage('Usage: show maxscale')
}, function(argv) {
maxctrl(argv, function(host) {
return getResource(host, 'maxscale', [
{'Version': 'attributes.version'},
{'Commit': 'attributes.commit'},
{'Started At': 'attributes.started_at'},
{'Activated At': 'attributes.activated_at'},
{'Uptime': 'attributes.uptime'},
{'Parameters': 'attributes.parameters'}
])
return getResource(host, 'maxscale', show_maxscale_fields)
})
})
.command('thread <thread>', 'Show thread', function(yargs) {
return yargs.epilog('Show detailed information about a worker thread.')
return yargs.epilog('Show detailed information about a worker thread.' +
fieldDescriptions(thread_fields))
.usage('Usage: show thread <thread>')
}, function(argv) {
maxctrl(argv, function(host) {
@ -259,7 +606,8 @@ exports.builder = function(yargs) {
})
})
.command('threads', 'Show all threads', function(yargs) {
return yargs.epilog('Show detailed information about all worker threads.')
return yargs.epilog('Show detailed information about all worker threads.' +
fieldDescriptions(thread_fields))
.usage('Usage: show threads')
}, function(argv) {
maxctrl(argv, function(host) {
@ -268,28 +616,21 @@ exports.builder = function(yargs) {
})
.command('logging', 'Show MaxScale logging information', function(yargs) {
return yargs.epilog('See `help alter logging` for more details about altering ' +
'logging parameters.')
'logging parameters.' + fieldDescriptions(show_logging_fields))
.usage('Usage: show logging')
}, function(argv) {
maxctrl(argv, function(host) {
return getResource(host, 'maxscale/logs', [
{'Current Log File': 'attributes.log_file'},
{'Enabled Log Levels': 'attributes.log_priorities'},
{'Parameters': 'attributes.parameters'}
])
return getResource(host, 'maxscale/logs', show_logging_fields)
})
})
.command('commands <module>', 'Show module commands of a module', function(yargs) {
return yargs.epilog('This command shows the parameters the command expects with ' +
'the parameter descriptions.')
'the parameter descriptions.' + fieldDescriptions(show_commands_fields))
.usage('Usage: show commands <module>')
}, function(argv) {
maxctrl(argv, function(host) {
return getSubCollection(host, 'maxscale/modules/' + argv.module, 'attributes.commands', [
{'Command': 'id'},
{'Parameters': 'attributes.parameters[].type'},
{'Descriptions': 'attributes.parameters[].description'}
])
return getSubCollection(host, 'maxscale/modules/' + argv.module, 'attributes.commands',
show_commands_fields)
})
})
.command('qc_cache', 'Show query classifier cache', function(yargs) {

View File

@ -1259,7 +1259,7 @@ mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char*
return rval;
}
static inline bool is_next(mxs::Buffer::iterator it, mxs::Buffer::iterator end, const std::string& str)
static inline bool is_next(uint8_t* it, uint8_t* end, const std::string& str)
{
mxb_assert(it != end);
for (auto s_it = str.begin(); s_it != str.end(); ++s_it, ++it)
@ -1316,12 +1316,11 @@ static const LUT is_special([](uint8_t c) {
c) != std::string::npos;
});
static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator it,
mxs::Buffer::iterator end)
static inline std::pair<bool, uint8_t*> probe_number(uint8_t* it, uint8_t* end)
{
mxb_assert(it != end);
mxb_assert(is_digit(*it));
std::pair<bool, mxs::Buffer::iterator> rval = std::make_pair(true, it);
std::pair<bool, uint8_t*> rval = std::make_pair(true, it);
bool is_hex = *it == '0';
bool allow_hex = false;
@ -1348,7 +1347,7 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
else if (*it == 'e')
{
// Possible scientific notation number
auto next_it = std::next(it);
auto next_it = it + 1;
if (next_it == end || (!is_digit(*next_it) && *next_it != '-'))
{
@ -1365,7 +1364,7 @@ static std::pair<bool, mxs::Buffer::iterator> probe_number(mxs::Buffer::iterator
else if (*it == '.')
{
// Possible decimal number
auto next_it = std::next(it);
auto next_it = it + 1;
if (next_it != end && !is_digit(*next_it))
{
@ -1415,7 +1414,7 @@ static inline bool is_negation(const std::string& str, int i)
return rval;
}
mxs::Buffer::iterator find_char(mxs::Buffer::iterator it, const mxs::Buffer::iterator& end, char c)
static inline uint8_t* find_char(uint8_t* it, uint8_t* end, char c)
{
for (; it != end; ++it)
{
@ -1440,13 +1439,13 @@ namespace maxscale
std::string get_canonical(GWBUF* querybuf)
{
std::string rval;
mxb_assert(GWBUF_IS_CONTIGUOUS(querybuf));
uint8_t* it = GWBUF_DATA(querybuf) + MYSQL_HEADER_LEN + 1;
uint8_t* end = GWBUF_DATA(querybuf) + gwbuf_length(querybuf);
std::string rval(end - it, 0);
int i = 0;
rval.resize(gwbuf_length(querybuf) - MYSQL_HEADER_LEN + 1);
mxs::Buffer buf(querybuf);
for (auto it = std::next(buf.begin(), MYSQL_HEADER_LEN + 1); // Skip packet header and command
it != buf.end(); ++it)
for (; it != end; ++it)
{
if (!is_special(*it))
{
@ -1456,9 +1455,9 @@ std::string get_canonical(GWBUF* querybuf)
else if (*it == '\\')
{
// Jump over any escaped values
rval[i++] += *it++;
rval[i++] = *it++;
if (it != buf.end())
if (it != end)
{
rval[i++] = *it;
}
@ -1479,19 +1478,19 @@ std::string get_canonical(GWBUF* querybuf)
rval[i++] = ' ';
}
}
else if (*it == '/' && is_next(it, buf.end(), "/*"))
else if (*it == '/' && is_next(it, end, "/*"))
{
auto comment_start = std::next(it, 2);
if (comment_start == buf.end())
if (comment_start == end)
{
break;
}
else if (*comment_start != '!' && *comment_start != 'M')
{
// Non-executable comment
while (it != buf.end())
while (it != end)
{
if (is_next(it, buf.end(), "*/"))
if (is_next(it, end, "*/"))
{
// Comment end marker, return to normal parsing
++it;
@ -1500,7 +1499,7 @@ std::string get_canonical(GWBUF* querybuf)
++it;
}
if (it == buf.end())
if (it == end)
{
break;
}
@ -1512,10 +1511,10 @@ std::string get_canonical(GWBUF* querybuf)
}
}
else if ((*it == '#' || *it == '-')
&& (is_next(it, buf.end(), "# ") || is_next(it, buf.end(), "-- ")))
&& (is_next(it, end, "# ") || is_next(it, end, "-- ")))
{
// End-of-line comment, jump to the next line if one exists
while (it != buf.end())
while (it != end)
{
if (*it == '\n')
{
@ -1523,7 +1522,7 @@ std::string get_canonical(GWBUF* querybuf)
}
else if (*it == '\r')
{
if ((is_next(it, buf.end(), "\r\n")))
if ((is_next(it, end, "\r\n")))
{
++it;
}
@ -1533,14 +1532,14 @@ std::string get_canonical(GWBUF* querybuf)
++it;
}
if (it == buf.end())
if (it == end)
{
break;
}
}
else if (is_digit(*it) && (i == 0 || (!is_alnum(rval[i - 1]) && rval[i - 1] != '_')))
{
auto num_end = probe_number(it, buf.end());
auto num_end = probe_number(it, end);
if (num_end.first)
{
@ -1556,7 +1555,7 @@ std::string get_canonical(GWBUF* querybuf)
else if (*it == '\'' || *it == '"')
{
char c = *it;
if ((it = find_char(std::next(it), buf.end(), c)) == buf.end())
if ((it = find_char(it + 1, end, c)) == end)
{
break;
}
@ -1565,7 +1564,7 @@ std::string get_canonical(GWBUF* querybuf)
else if (*it == '`')
{
auto start = it;
if ((it = find_char(std::next(it), buf.end(), '`')) == buf.end())
if ((it = find_char(it + 1, end, '`')) == end)
{
break;
}
@ -1578,7 +1577,7 @@ std::string get_canonical(GWBUF* querybuf)
rval[i++] = *it;
}
mxb_assert(it != buf.end());
mxb_assert(it != end);
}
// Remove trailing whitespace
@ -1590,8 +1589,6 @@ std::string get_canonical(GWBUF* querybuf)
// Shrink the buffer so that the internal bookkeeping of std::string remains up to date
rval.resize(i);
buf.release();
return rval;
}
}

View File

@ -208,6 +208,24 @@ void ResultSet::write(DCB* dcb)
mysql_send_eof(dcb, seqno);
}
json_t* ResultSet::get_json_value(const std::string& s)
{
json_t* js;
char* end;
long l = strtol(s.c_str(), &end, 10);
if (end != s.c_str() && *end == '\0')
{
js = json_integer(l);
}
else
{
js = json_string(s.c_str());
}
return js;
}
void ResultSet::write_as_json(DCB* dcb)
{
json_t* arr = json_array();
@ -218,7 +236,7 @@ void ResultSet::write_as_json(DCB* dcb)
for (size_t i = 0; i < row.size(); i++)
{
json_object_set_new(obj, m_columns[i].c_str(), json_string(row[i].c_str()));
json_object_set_new(obj, m_columns[i].c_str(), get_json_value(row[i]));
}
json_array_append_new(arr, obj);
@ -227,4 +245,6 @@ void ResultSet::write_as_json(DCB* dcb)
char* js = json_dumps(arr, JSON_INDENT(4));
dcb_printf(dcb, "%s", js);
MXS_FREE(js);
json_decref(arr);
}

View File

@ -62,7 +62,7 @@ struct
uint64_t next_session_id;
uint32_t retain_last_statements;
session_dump_statements_t dump_statements;
uint32_t session_trace;
uint32_t session_trace;
} this_unit =
{
1,
@ -160,7 +160,6 @@ bool session_start(MXS_SESSION* session)
session->state = SESSION_STATE_STARTED;
mxb::atomic::add(&session->service->stats.n_sessions, 1, mxb::atomic::RELAXED);
mxb::atomic::add(&session->service->stats.n_current, 1, mxb::atomic::RELAXED);
MXS_INFO("Started %s client session [%" PRIu64 "] for '%s' from %s",
session->service->name(), session->ses_id,
@ -279,8 +278,6 @@ static void session_final_free(MXS_SESSION* ses)
session->state = SESSION_STATE_TO_BE_FREED;
mxb::atomic::add(&session->service->stats.n_current, -1, mxb::atomic::RELAXED);
if (session->client_dcb)
{
dcb_free_all_memory(session->client_dcb);
@ -1162,6 +1159,9 @@ Session::Session(const SListener& listener)
{
m_retain_last_statements = this_unit.retain_last_statements;
}
mxb::atomic::add(&service->stats.n_current, 1, mxb::atomic::RELAXED);
mxb_assert(service->stats.n_current >= 0);
}
Session::~Session()
@ -1176,6 +1176,9 @@ Session::~Session()
f.filter->obj->closeSession(f.instance, f.session);
f.filter->obj->freeSession(f.instance, f.session);
}
mxb::atomic::add(&service->stats.n_current, -1, mxb::atomic::RELAXED);
mxb_assert(service->stats.n_current >= 0);
}
void Session::set_client_dcb(DCB* dcb)
@ -1696,6 +1699,6 @@ void Session::dump_session_log()
log += s;
}
MXS_NOTICE("Session log for session (%" PRIu64"): \n%s ", ses_id, log.c_str());
MXS_NOTICE("Session log for session (%" PRIu64 "): \n%s ", ses_id, log.c_str());
}
}
}

View File

@ -1,4 +1,5 @@
add_executable(profile_trxboundaryparser profile_trxboundaryparser.cc)
add_executable(profile_get_canonical profile_get_canonical.cc)
add_executable(test_adminusers test_adminusers.cc)
add_executable(test_atomic test_atomic.cc)
add_executable(test_buffer test_buffer.cc)
@ -28,6 +29,7 @@ add_executable(test_utils test_utils.cc)
add_executable(test_session_track test_session_track.cc)
target_link_libraries(profile_trxboundaryparser maxscale-common)
target_link_libraries(profile_get_canonical maxscale-common)
target_link_libraries(test_adminusers maxscale-common)
target_link_libraries(test_atomic maxscale-common)
target_link_libraries(test_buffer maxscale-common)

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/ccdefs.hh>
#include <maxscale/modutil.hh>
#include <iostream>
#include <sstream>
#include <chrono>
using Clock = std::chrono::steady_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
int main(int argc, char* argv[])
{
int ITERATIONS = 10000000;
for (std::string line; std::getline(std::cin, line);)
{
GWBUF* buf = modutil_create_query(line.c_str());
auto start = Clock::now();
for (int i = 0; i < ITERATIONS; i++)
{
auto str = mxs::get_canonical(buf);
}
auto end = Clock::now();
gwbuf_free(buf);
std::cout << line << "\n"
<< duration_cast<milliseconds>(end - start).count() << "ms\n\n";
}
return 0;
}

View File

@ -26,8 +26,6 @@ Session::Session(Client* pClient, const SListener& listener)
{
MXS_SESSION* pSession = this;
memset((void*)pSession, 0, sizeof(MXS_SESSION));
pSession->state = SESSION_STATE_CREATED;
pSession->client_dcb = &m_client_dcb;

View File

@ -1419,13 +1419,15 @@ static void worker_func(int thread_id, void* data)
for (TargetList::iterator it = info->targets.begin();
it != info->targets.end(); it++)
{
LocalClient* client = LocalClient::create(&info->session, &info->protocol, it->first);
GWBUF* buffer = modutil_create_query(it->second.c_str());
client->queue_query(buffer);
gwbuf_free(buffer);
if (LocalClient* client = LocalClient::create(&info->session, &info->protocol, it->first))
{
GWBUF* buffer = modutil_create_query(it->second.c_str());
client->queue_query(buffer);
gwbuf_free(buffer);
// The LocalClient needs to delete itself once the queries are done
client->self_destruct();
// The LocalClient needs to delete itself once the queries are done
client->self_destruct();
}
}
delete info;