root / plugins / mysql / mysql_disk_by_prefix @ ee226a60
Historique | Voir | Annoter | Télécharger (3,57 ko)
| 1 | ee226a60 | pcy | #!/usr/bin/env python3 |
|---|---|---|---|
| 2 | # -*- python -*- |
||
| 3 | |||
| 4 | """ |
||
| 5 | =head1 INTRODUCTION |
||
| 6 | |||
| 7 | Plugin to monitor MySQL database disk usage per prefix. A prefix can be eg. |
||
| 8 | 'user' for the databases 'user_testdb' and 'user_db2', as can be seen in |
||
| 9 | certain shared hosting setups. |
||
| 10 | |||
| 11 | =head1 APPLICABLE SYSTEMS |
||
| 12 | |||
| 13 | Local access to the database files is required (which are stored in |
||
| 14 | /var/lib/mysql by default). |
||
| 15 | |||
| 16 | =head1 INSTALLATION |
||
| 17 | |||
| 18 | Place in /etc/munin/plugins/ (or link it there using ln -s) |
||
| 19 | |||
| 20 | =head1 CONFIGURATION |
||
| 21 | |||
| 22 | Add this to your /etc/munin/plugin-conf.d/munin-node: |
||
| 23 | |||
| 24 | =over 2 |
||
| 25 | |||
| 26 | [mysql_disk_by_prefix] |
||
| 27 | user mysql |
||
| 28 | env.prefixes_with_underscores foo_bar d_ritchie # prefixes that include an underscore |
||
| 29 | env.db_minsize 1024000 # minimum db size in order to report a specific prefix, |
||
| 30 | # in bytes (defaults to 50M). "0" for none. |
||
| 31 | env.mysql_db_dir /var/lib/mysql # default value |
||
| 32 | |||
| 33 | =back |
||
| 34 | |||
| 35 | =head1 HISTORY |
||
| 36 | |||
| 37 | 2019-07-25: v 1.0 pcy <pcy.ulyssis.org>: created |
||
| 38 | |||
| 39 | =head1 USAGE |
||
| 40 | |||
| 41 | Parameters understood: |
||
| 42 | |||
| 43 | config (required) |
||
| 44 | autoconf (optional - used by munin-config) |
||
| 45 | |||
| 46 | =head1 MAGIC MARKERS |
||
| 47 | |||
| 48 | #%# family=auto |
||
| 49 | #%# capabilities=autoconf |
||
| 50 | """ |
||
| 51 | |||
| 52 | |||
| 53 | import os |
||
| 54 | import re |
||
| 55 | import subprocess as sp |
||
| 56 | import sys |
||
| 57 | |||
| 58 | |||
| 59 | def weakbool(x): |
||
| 60 | return x.lower().strip() in {'true', 'yes', 'y', 1}
|
||
| 61 | |||
| 62 | |||
| 63 | LIMIT = os.getenv('db_minsize', str(50000 * 1024))
|
||
| 64 | try: |
||
| 65 | LIMIT = int(LIMIT) |
||
| 66 | except ValueError: |
||
| 67 | LIMIT = 0 |
||
| 68 | |||
| 69 | exceptions = os.getenv('prefix_with_underscores', '').split(' ')
|
||
| 70 | if exceptions == ['']: |
||
| 71 | exceptions = [] |
||
| 72 | |||
| 73 | mysqldir = os.getenv('mysql_db_dir', '/var/lib/mysql')
|
||
| 74 | |||
| 75 | |||
| 76 | def name_from_path(path): |
||
| 77 | filename = os.path.basename(path) |
||
| 78 | for name in exceptions: |
||
| 79 | if filename.startswith(name): |
||
| 80 | return name |
||
| 81 | name = filename.split('_')[0]
|
||
| 82 | |||
| 83 | def decode_byte(m): |
||
| 84 | return bytes.fromhex(m.group(1)).decode('utf-8')
|
||
| 85 | |||
| 86 | # Decode MySQL's encoding of non-ascii characters in the table names |
||
| 87 | return re.sub('@00([0-9a-z]{2})', decode_byte, name)
|
||
| 88 | |||
| 89 | |||
| 90 | def calc_dir_size(directory): |
||
| 91 | total = 0 |
||
| 92 | |||
| 93 | for filename in os.listdir(directory): |
||
| 94 | filedir = os.path.join(directory, filename) |
||
| 95 | |||
| 96 | if os.path.islink(filedir): |
||
| 97 | continue |
||
| 98 | if os.path.isfile(filedir): |
||
| 99 | total += os.path.getsize(filedir) |
||
| 100 | |||
| 101 | return total |
||
| 102 | |||
| 103 | |||
| 104 | def size_per_subdir(parentdir): |
||
| 105 | for subdir in os.listdir(parentdir): |
||
| 106 | dirpath = os.path.join(parentdir, subdir) |
||
| 107 | |||
| 108 | if os.path.islink(dirpath): |
||
| 109 | continue |
||
| 110 | if os.path.isdir(dirpath): |
||
| 111 | yield calc_dir_size(dirpath), name_from_path(os.path.split(dirpath)[1]) |
||
| 112 | |||
| 113 | |||
| 114 | def sizes_by_name(limit=None): |
||
| 115 | sizes = {}
|
||
| 116 | for size, name in size_per_subdir(mysqldir): |
||
| 117 | sizes[name] = sizes.get(name, 0) + size |
||
| 118 | for name, total_size in sizes.items(): |
||
| 119 | if limit <= 0 or limit <= total_size: |
||
| 120 | yield name, total_size |
||
| 121 | |||
| 122 | |||
| 123 | def fetch(): |
||
| 124 | for name, total_size in sorted(sizes_by_name(limit=LIMIT), key=lambda x: x[0]): |
||
| 125 | print('{}.value {}'.format(name, total_size))
|
||
| 126 | |||
| 127 | |||
| 128 | def main(): |
||
| 129 | if len(sys.argv) == 1: |
||
| 130 | fetch() |
||
| 131 | elif sys.argv[1] == 'config': |
||
| 132 | print('graph_title MySQL disk usage by prefix')
|
||
| 133 | print('graph_vlabel bytes')
|
||
| 134 | print('graph_category db')
|
||
| 135 | |||
| 136 | names = sorted(name for name, _ in sizes_by_name(limit=LIMIT)) |
||
| 137 | for name in names: |
||
| 138 | print('{0}.label {0}'.format(name))
|
||
| 139 | print('{}.type GAUGE'.format(name))
|
||
| 140 | print('{}.draw AREASTACK'.format(name))
|
||
| 141 | elif sys.argv[1] == 'autoconf': |
||
| 142 | print('yes' if os.path.isdir(mysqldir)
|
||
| 143 | else "no (can't find MySQL's data directory)") |
||
| 144 | else: |
||
| 145 | fetch() |
||
| 146 | |||
| 147 | |||
| 148 | if __name__ == "__main__": |
||
| 149 | main() |
||
| 150 | |||
| 151 | # flake8: noqa: E265 |
