-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle variations in the user_statistics collecting #4306
Conversation
MariaDB has two different table definitions: - https://mariadb.com/kb/en/library/information-schema-user_statistics-table/ Percona has it's own: - https://www.percona.com/doc/percona-server/LATEST/diagnostics/user_stats.html#USER_STATISTICS MySQL doesn't have the table, but there may be a patch: - https://www.byte.nl/blog/enhancing-mysql-with-user-statistics Resolves #2910 Closes #3382
plugins/inputs/mysql/mysql.go
Outdated
|
||
acc.AddFields("mysql_user_stats", fields, tags) | ||
// disable collecting if table is not found (suppresses repeat errors) | ||
if strings.Contains(err.Error(), "nknown table 'user_statistics'") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally leave out capital letters when matching error strings in case the error changes. (nknown
is intended)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, personally I would probably just ToLower it, but ideally this type of error matching will be uncommon.
I did find an interesting link off of http://go-database-sql.org/errors.html#identifying-specific-database-errors to a package of error codes for mysql, this might be helpful, though I'm unsure if these work on all mysql flavors.
plugins/inputs/mysql/mysql.go
Outdated
@@ -790,72 +790,13 @@ func (m *Mysql) gatherGlobalStatuses(db *sql.DB, serv string, acc telegraf.Accum | |||
|
|||
// gather connection metrics from user_statistics for each user | |||
if m.GatherUserStatistics { | |||
conn_rows, err := db.Query("select user, total_connections, concurrent_connections, connected_time, busy_time, cpu_time, bytes_received, bytes_sent, binlog_bytes_written, rows_fetched, rows_updated, table_rows_read, select_commands, update_commands, other_commands, commit_transactions, rollback_transactions, denied_connections, lost_connections, access_denied, empty_queries, total_ssl_connections FROM INFORMATION_SCHEMA.USER_STATISTICS GROUP BY user") | |||
err = m.GatherUserStatisticsStatuses(db, serv, acc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this result in this function being called twice per interval, once from gatherServer
and once here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It didn't appear to in testing (unless I missed it), but all the logic was already happening at this location anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about L455 though?
plugins/inputs/mysql/mysql.go
Outdated
} | ||
acc.AddFields("mysql_user_stats", fields, tags) | ||
} | ||
return nil | ||
} | ||
|
||
// convertColumns converts selected column names to lowercase. | ||
func convertColumns(s []string, e error) ([]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename columnsToLower
or lowerColumns
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea
plugins/inputs/mysql/mysql.go
Outdated
count uint32 | ||
) | ||
|
||
cols, err := convertColumns(rows.Columns()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an observation. I never knew you could pass multiple arguments like this, interesting. I'm not sure I like how it becomes the outer functions job to deal with an upstream error, but I like how it can compress the error handling needed in this function.
"total_ssl_connections": total_ssl_connections, | ||
for i := range cols { | ||
if i == 0 { | ||
continue // skip "user" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Percona index 0 comment applies here.
"access_denied": access_denied, | ||
"empty_queries": empty_queries, | ||
"total_ssl_connections": total_ssl_connections, | ||
for i := range cols { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use for _, column := range cols {
and avoid using the index directly. I know in some situations it can be slightly slower to iterate in this manner, but I think it almost always is more readable and less error prone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, I think leaving it is best. I'll still need the iterator for the read
slice and it's pretty clear where cols[i]
is only used in the switch block.
plugins/inputs/mysql/mysql.go
Outdated
continue // skip "user" | ||
} | ||
switch v := read[i].(type) { | ||
case *bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only include expected types and the default case can handle anything else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Is it stylistically ok to leave out the default case since it is immediately followed by the function's return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually leave the default in to catch any programming mistakes.
if err != nil { | ||
return err | ||
} | ||
|
||
tags := map[string]string{"server": servtag, "user": user} | ||
fields := map[string]interface{}{ | ||
tags := map[string]string{"server": servtag, "user": *read[0].(*string)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User is not always index 0 (percona)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make it more robust, but in all the docs I checked it would be index 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about in this list of columns?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alias for user
iirc, though now i'm not sure..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AH! I was referencing client_statistics
table instead of user_statistics
table
plugins/inputs/mysql/mysql.go
Outdated
return err | ||
} | ||
|
||
if len(read) != len(cols) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably remove this check, since in theory it is impossible unless there is a programming error and it should. be picked up by the rows.Scan(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Closes #1645 |
(cherry picked from commit 54056f3)
…uxdata#4306) (cherry picked from commit 54056f3)
There are differences in the
user_statistics
table (if it exists) between MariaDB, Percona, and MySQL. This should allow the stats to be collected properly.MariaDB has two different table definitions:
Percona has it's own:
MySQL doesn't have the table, but there may be a patch:
Resolves #2910
Closes #3382
Required for all PRs: