add userstats charts for mysql (#6118)
* add userstats charts for mysql
Oleksii Kravchenko committed
Jun 27, 2019 at 11:33 UTC
6fc971fe4d070d7c3e666ee24fb0c43d43084c85
2 files changed
+145
-2
collectors/python.d.plugin/mysql/README.md
+21
-1
@@ -218,6 +218,24 @@ It will produce following charts (if data is available):
218
45. **Flow Control** in ms
219
* paused
220
221
+46. **Users CPU time** in percentage
222
+ * users
223
+
224
+**Per user statistics:**
225
+
226
+1. **Rows Operations** in operations/s
227
+ * read
228
+ * send
229
+ * updated
230
+ * inserted
231
+ * deleted
232
+
233
+2. **Commands** in commands/s
234
+ * select
235
+ * update
236
+ * other
237
+
238
+
239
### configuration
240
241
You can provide, per server, the following:
@@ -234,7 +252,7 @@ You can provide, per server, the following:
252
- ca: the path name of the Certificate Authority (CA) certificate file. This option, if used, must specify the same certificate used by the server.
253
- capath: the path name of the directory that contains trusted SSL CA certificate files.
254
- cipher: the list of permitted ciphers for SSL encryption.
237
-
255
+
256
Here is an example for 3 servers:
257
258
```yaml
@@ -260,6 +278,8 @@ remote:
278
279
If no configuration is given, module will attempt to connect to mysql server via unix socket at `/var/run/mysqld/mysqld.sock` without password and with username `root`
280
281
+`userstats` graph works only if you enable such plugin in MariaDB server and set proper mysql priviliges (SUPER or PROCESS). For more detail please check [MariaDB User Statistics page](https://mariadb.com/kb/en/library/user-statistics/)
282
+
283
---
284
285
[]()
collectors/python.d.plugin/mysql/mysql.chart.py
+124
-1
@@ -11,6 +11,7 @@ from bases.FrameworkServices.MySQLService import MySQLService
11
QUERY_GLOBAL = 'SHOW GLOBAL STATUS;'
12
QUERY_SLAVE = 'SHOW SLAVE STATUS;'
13
QUERY_VARIABLES = 'SHOW GLOBAL VARIABLES LIKE \'max_connections\';'
14
+QUERY_USER_STATISTICS = 'SHOW USER_STATISTICS;'
15
16
GLOBAL_STATS = [
17
'Bytes_received',
@@ -150,6 +151,18 @@ SLAVE_STATS = [
151
('Slave_IO_Running', slave_running)
152
]
153
154
+USER_STATISTICS = [
155
+ 'Select_commands',
156
+ 'Update_commands',
157
+ 'Other_commands',
158
+ 'Cpu_time',
159
+ 'Rows_read',
160
+ 'Rows_sent',
161
+ 'Rows_deleted',
162
+ 'Rows_inserted',
163
+ 'Rows_updated'
164
+]
165
+
166
VARIABLES = [
167
'max_connections'
168
]
@@ -202,7 +215,8 @@ ORDER = [
215
'galera_bytes',
216
'galera_queue',
217
'galera_conflicts',
205
- 'galera_flow_control'
218
+ 'galera_flow_control',
219
+ 'userstats_cpu'
220
]
221
222
CHARTS = {
@@ -579,10 +593,45 @@ CHARTS = {
593
'lines': [
594
['wsrep_flow_control_paused_ns', 'paused', 'incremental', 1, 1000000],
595
]
596
+ },
597
+ 'userstats_cpu': {
598
+ 'options': [None, 'Users CPU time', 'percentage', 'userstats', 'mysql.userstats_cpu', 'stacked'],
599
+ 'lines': []
600
}
601
}
602
603
604
+def userstats_chart_template(name):
605
+ order = [
606
+ 'userstats_rows_{0}'.format(name),
607
+ 'userstats_commands_{0}'.format(name)
608
+ ]
609
+ family = 'userstats {0}'.format(name)
610
+
611
+ charts = {
612
+ order[0]: {
613
+ 'options': [None, 'Rows Operations', 'operations/s', family, 'mysql.userstats_rows', 'stacked'],
614
+ 'lines': [
615
+ ['userstats_{0}_Rows_read'.format(name), 'read', 'incremental'],
616
+ ['userstats_{0}_Rows_send'.format(name), 'send', 'incremental'],
617
+ ['userstats_{0}_Rows_updated'.format(name), 'updated', 'incremental'],
618
+ ['userstats_{0}_Rows_inserted'.format(name), 'inserted', 'incremental'],
619
+ ['userstats_{0}_Rows_deleted'.format(name), 'deleted', 'incremental']
620
+ ]
621
+ },
622
+ order[1]: {
623
+ 'options': [None, 'Commands', 'commands/s', family, 'mysql.userstats_commands', 'stacked'],
624
+ 'lines': [
625
+ ['userstats_{0}_Select_commands'.format(name), 'select', 'incremental'],
626
+ ['userstats_{0}_Update_commands'.format(name), 'update', 'incremental'],
627
+ ['userstats_{0}_Other_commands'.format(name), 'other', 'incremental']
628
+ ]
629
+ }
630
+ }
631
+
632
+ return order, charts
633
+
634
+
635
class Service(MySQLService):
636
def __init__(self, configuration=None, name=None):
637
MySQLService.__init__(self, configuration=configuration, name=name)
@@ -592,6 +641,7 @@ class Service(MySQLService):
641
global_status=QUERY_GLOBAL,
642
slave_status=QUERY_SLAVE,
643
variables=QUERY_VARIABLES,
644
+ user_statistics=QUERY_USER_STATISTICS,
645
)
646
647
def _get_data(self):
@@ -621,6 +671,12 @@ class Service(MySQLService):
671
else:
672
self.queries.pop('slave_status')
673
674
+ if 'user_statistics' in raw_data:
675
+ if raw_data['user_statistics'][0]:
676
+ to_netdata.update(self.get_userstats(raw_data))
677
+ else:
678
+ self.queries.pop('user_statistics')
679
+
680
if 'variables' in raw_data:
681
variables = dict(raw_data['variables'][0])
682
for key in VARIABLES:
@@ -628,3 +684,70 @@ class Service(MySQLService):
684
to_netdata[key] = variables[key]
685
686
return to_netdata or None
687
+
688
+ # raw_data['user_statistics'] contains the following data structure:
689
+ # (
690
+ # (
691
+ # ('netdata', 42L, 0L, 1264L, 3.111252999999968, 2.968510299999994, 110267L, 19741424L, 0L, 0L, 1265L, 0L,
692
+ # 0L, 0L, 3L, 0L, 1301L, 0L, 0L, 7633L, 0L, 83L, 44L, 0L, 0L),
693
+ # ('root', 60L, 0L, 184L, 0.22856499999999966, 0.1601419999999998, 11605L, 1516513L, 0L, 9L, 220L, 0L, 2L, 1L,
694
+ # 6L, 4L,127L, 0L, 0L, 45L, 0L, 45L, 0L, 0L, 0L)
695
+ # ),
696
+ # (
697
+ # ('User', 253, 9, 128, 128, 0, 0),
698
+ # ('Total_connections', 3, 2, 11, 11, 0, 0),
699
+ # ('Concurrent_connections', 3, 1, 11, 11, 0, 0),
700
+ # ('Connected_time', 3, 4, 11, 11, 0, 0),
701
+ # ('Busy_time', 5, 21, 21, 21, 31, 0),
702
+ # ('Cpu_time', 5, 18, 21, 21, 31, 0),
703
+ # ('Bytes_received', 8, 6, 21, 21, 0, 0),
704
+ # ('Bytes_sent', 8, 8, 21, 21, 0, 0),
705
+ # ('Binlog_bytes_written', 8, 1, 21, 21, 0, 0),
706
+ # ('Rows_read', 8, 1, 21, 21, 0, 0),
707
+ # ('Rows_sent', 8, 4, 21, 21, 0, 0),
708
+ # ('Rows_deleted', 8, 1, 21, 21, 0, 0),
709
+ # ('Rows_inserted', 8, 1, 21, 21, 0, 0),
710
+ # ('Rows_updated', 8, 1, 21, 21, 0, 0),
711
+ # ('Select_commands', 8, 1, 21, 21, 0, 0),
712
+ # ('Update_commands', 8, 1, 21, 21, 0, 0),
713
+ # ('Other_commands', 8, 4, 21, 21, 0, 0),
714
+ # ('Commit_transactions', 8, 1, 21, 21, 0, 0),
715
+ # ('Rollback_transactions', 8, 1, 21, 21, 0, 0),
716
+ # ('Denied_connections', 8, 4, 21, 21, 0, 0),
717
+ # ('Lost_connections', 8, 1, 21, 21, 0, 0),
718
+ # ('Access_denied', 8, 2, 21, 21, 0, 0),
719
+ # ('Empty_queries', 8, 2, 21, 21, 0, 0),
720
+ # ('Total_ssl_connections', 8, 1, 21, 21, 0, 0),
721
+ # ('Max_statement_time_exceeded', 8, 1, 21, 21, 0, 0)),
722
+ # )
723
+ def get_userstats(self, raw_data):
724
+ data = dict()
725
+ userstats_vars = [e[0] for e in raw_data['user_statistics'][1]]
726
+ for i, _ in enumerate(raw_data['user_statistics'][0]):
727
+ user_name = raw_data['user_statistics'][0][i][0]
728
+ userstats = dict(zip(userstats_vars, raw_data['user_statistics'][0][i]))
729
+
730
+ if len(self.charts) > 0:
731
+ if ('userstats_{0}_Cpu_time'.format(user_name)) not in self.charts['userstats_cpu']:
732
+ self.add_userstats_dimensions(user_name)
733
+ self.create_new_userstats_charts(user_name)
734
+
735
+ for key in USER_STATISTICS:
736
+ if key in userstats:
737
+ data['userstats_{0}_{1}'.format(user_name, key)] = userstats[key]
738
+
739
+ return data
740
+
741
+ def add_userstats_dimensions(self, name):
742
+ self.charts['userstats_cpu'].add_dimension(['userstats_{0}_Cpu_time'.format(name), name, 'incremental', 100, 1])
743
+
744
+ def create_new_userstats_charts(self, tube):
745
+ order, charts = userstats_chart_template(tube)
746
+
747
+ for chart_name in order:
748
+ params = [chart_name] + charts[chart_name]['options']
749
+ dimensions = charts[chart_name]['lines']
750
+
751
+ new_chart = self.charts.add_chart(params)
752
+ for dimension in dimensions:
753
+ new_chart.add_dimension(dimension)