add mysql InnoDB op/sec to performance dashboards

This commit is contained in:
Spine
2025-07-28 05:35:52 +00:00
parent a1329533b6
commit be151fab96
10 changed files with 61 additions and 13 deletions

View File

@@ -34,7 +34,7 @@ class MysqlInfo extends \Gazelle\Base {
}
self::$db->prepared_query("
SELECT $tableColumn AS table_name,
SELECT t.$tableColumn AS table_name,
t.ENGINE AS engine,
t.ROW_FORMAT AS row_format,
sum(t.TABLE_ROWS) AS table_rows,
@@ -44,10 +44,21 @@ class MysqlInfo extends \Gazelle\Base {
sum(t.INDEX_LENGTH) AS index_length,
sum(t.INDEX_LENGTH + t.DATA_LENGTH) AS total_length,
sum(t.DATA_FREE) AS data_free,
CASE WHEN sum(t.DATA_LENGTH) = 0 THEN 0 ELSE sum(t.DATA_FREE) / sum(t.DATA_LENGTH) END as free_ratio
CASE WHEN sum(t.DATA_LENGTH) = 0 THEN 0 ELSE sum(t.DATA_FREE) / sum(t.DATA_LENGTH) END as free_ratio,
CASE WHEN wsbt.COUNT_READ + wsbt.COUNT_WRITE + wsbt.COUNT_FETCH
+ wsbt.COUNT_INSERT + wsbt.COUNT_UPDATE + wsbt.COUNT_DELETE = 0
THEN 0
ELSE
(wsbt.COUNT_READ + wsbt.COUNT_WRITE + wsbt.COUNT_FETCH
+ wsbt.COUNT_INSERT + wsbt.COUNT_UPDATE + wsbt.COUNT_DELETE)
/ (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime')
END AS iops
FROM information_schema.tables t
LEFT JOIN information_schema.table_statistics ts USING (table_schema, table_name)
WHERE table_schema = ? $where
INNER JOIN performance_schema.table_io_waits_summary_by_table wsbt
ON (wsbt.OBJECT_SCHEMA = t.TABLE_SCHEMA AND wsbt.OBJECT_NAME = t.TABLE_NAME)
LEFT JOIN information_schema.table_statistics ts
ON (ts.TABLE_SCHEMA = t.TABLE_SCHEMA AND ts.TABLE_NAME = t.TABLE_NAME)
WHERE t.table_schema = ? $where
GROUP BY $tableColumn, engine, row_format
ORDER BY {$this->orderBy->value} {$this->direction->value}
", MYSQL_DB
@@ -75,6 +86,8 @@ class MysqlInfo extends \Gazelle\Base {
=> ['dbColumn' => MysqlInfoOrderBy::freeRatio->value, 'defaultSort' => 'desc', 'text' => 'Bloat %', 'alt' => 'table bloat'],
MysqlInfoOrderBy::avgRowLength->value
=> ['dbColumn' => MysqlInfoOrderBy::avgRowLength->value, 'defaultSort' => 'desc', 'text' => 'Row Size', 'alt' => 'mean row length'],
MysqlInfoOrderBy::iops->value
=> ['dbColumn' => MysqlInfoOrderBy::iops->value, 'defaultSort' => 'desc', 'text' => 'IOPS', 'alt' => 'innodb ops sec'],
];
}
@@ -93,6 +106,7 @@ class MysqlInfo extends \Gazelle\Base {
MysqlInfoOrderBy::dataFree->value => MysqlInfoOrderBy::dataFree,
MysqlInfoOrderBy::freeRatio->value => MysqlInfoOrderBy::freeRatio,
MysqlInfoOrderBy::avgRowLength->value => MysqlInfoOrderBy::avgRowLength,
MysqlInfoOrderBy::iops->value => MysqlInfoOrderBy::iops,
};
}

View File

@@ -105,12 +105,21 @@ class MysqlTable extends AbstractTable {
t.DATA_LENGTH,
t.INDEX_LENGTH,
t.DATA_FREE,
ts.ROWS_READ,
ts.ROWS_CHANGED,
ts.ROWS_CHANGED_X_INDEXES
coalesce(ts.ROWS_READ, 0) AS ROWS_READ,
coalesce(ts.ROWS_CHANGED, 0) AS ROWS_CHANGED,
coalesce(ts.ROWS_CHANGED_X_INDEXES, 0) AS ROWS_CHANGED_X_INDEXES,
CASE WHEN wsbt.COUNT_READ + wsbt.COUNT_WRITE + wsbt.COUNT_FETCH
+ wsbt.COUNT_INSERT + wsbt.COUNT_UPDATE + wsbt.COUNT_DELETE = 0
THEN 0
ELSE (wsbt.COUNT_READ + wsbt.COUNT_WRITE + wsbt.COUNT_FETCH
+ wsbt.COUNT_INSERT + wsbt.COUNT_UPDATE + wsbt.COUNT_DELETE)
/ (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime')
END AS iops
FROM information_schema.tables t
INNER JOIN information_schema.table_statistics ts
USING (TABLE_SCHEMA, TABLE_NAME)
INNER JOIN performance_schema.table_io_waits_summary_by_table wsbt
ON (wsbt.OBJECT_SCHEMA = t.TABLE_SCHEMA AND wsbt.OBJECT_NAME = t.TABLE_NAME)
LEFT JOIN information_schema.table_statistics ts
ON (ts.TABLE_SCHEMA = t.TABLE_SCHEMA AND ts.TABLE_NAME = t.TABLE_NAME)
WHERE t.TABLE_SCHEMA = ?
AND t.TABLE_NAME = ?
", MYSQL_DB, $this->name

View File

@@ -13,4 +13,5 @@ enum MysqlInfoOrderBy: string {
case dataFree = 'data_free';
case freeRatio = 'free_ratio';
case avgRowLength = 'avg_row_length';
case iops = 'iops';
}

View File

@@ -63,5 +63,6 @@ GRANT EXECUTE ON FUNCTION `gazelle`.`binomial_ci` TO 'www'@'localhost';
GRANT EXECUTE ON FUNCTION `gazelle`.`bonus_accrual` TO 'www'@'localhost';
GRANT SELECT ON `sys`.`schema_unused_indexes` TO 'www'@'localhost';
GRANT SELECT ON `performance_schema`.`table_io_waits_summary_by_index_usage` TO 'www'@'localhost';
GRANT SELECT ON `performance_schema`.`table_io_waits_summary_by_table` TO 'www'@'localhost';
GRANT SELECT ON `sys`.`schema_redundant_indexes` TO 'www'@'localhost';
GRANT SELECT ON `sys`.`x$schema_flattened_keys` TO 'www'@'localhost';

View File

@@ -1,4 +1,5 @@
GRANT SELECT ON performance_schema.table_io_waits_summary_by_index_usage TO 'gazelle'@'%';
GRANT SELECT ON performance_schema.table_io_waits_summary_by_table TO 'gazelle'@'%';
GRANT SELECT ON sys.schema_redundant_indexes TO 'gazelle'@'%';
GRANT SELECT ON sys.schema_unused_indexes TO 'gazelle'@'%';
GRANT SELECT ON sys.x$schema_flattened_keys TO 'gazelle'@'%';

View File

@@ -59,10 +59,12 @@ if [ -z "${MYSQL_INIT_DB-}" ]; then
cat <<EOF
CREATE USER IF NOT EXISTS 'ro_$MYSQL_USER'@'%' IDENTIFIED BY 'ro_$MYSQL_PASSWORD';
GRANT SELECT ON performance_schema.table_io_waits_summary_by_index_usage TO '$MYSQL_USER'@'%';
GRANT SELECT ON performance_schema.table_io_waits_summary_by_table TO '$MYSQL_USER'@'%';
GRANT SELECT ON sys.schema_redundant_indexes TO '$MYSQL_USER'@'%';
GRANT SELECT ON sys.schema_unused_indexes TO '$MYSQL_USER'@'%';
GRANT SELECT ON sys.x\$schema_flattened_keys TO '$MYSQL_USER'@'%';
GRANT SELECT ON performance_schema.table_io_waits_summary_by_index_usage TO 'ro_$MYSQL_USER'@'%';
GRANT SELECT ON performance_schema.table_io_waits_summary_by_table TO 'ro_$MYSQL_USER'@'%';
GRANT SELECT ON sys.schema_redundant_indexes TO 'ro_$MYSQL_USER'@'%';
GRANT SELECT ON sys.schema_unused_indexes TO 'ro_$MYSQL_USER'@'%';
GRANT SELECT ON sys.x\$schema_flattened_keys TO 'ro_$MYSQL_USER'@'%';

View File

@@ -66,6 +66,7 @@ Highcharts.chart('statistics', {
<td style="text-align:right" class="nobr">{{ header|column('data_free') }}</td>
<td style="text-align:right" class="nobr">{{ header|column('free_ratio') }}</td>
<td style="text-align:right" class="nobr">{{ header|column('total_length') }}</td>
<td style="text-align:right" class="nobr">{{ header|column('iops') }}</td>
</tr>
{% set total_rows = 0 %}
@@ -73,12 +74,14 @@ Highcharts.chart('statistics', {
{% set total_data_size = 0 %}
{% set total_index_size = 0 %}
{% set total_free_size = 0 %}
{% set total_iops = 0 %}
{% for t in list -%}
{%- set total_rows = total_rows + t.table_rows -%}
{%- set total_rows_read = total_rows_read + t.rows_read -%}
{%- set total_data_size = total_data_size + t.data_length -%}
{%- set total_index_size = total_index_size + t.index_length -%}
{%- set total_free_size = total_free_size + t.data_free -%}
{%- set total_iops = total_iops + t.iops -%}
<tr class="row{{ cycle(['a', 'b'], loop.index0) }}">
<td><a href="tools.php?action=db-mysql&amp;table={{ t.table_name }}" title="engine: {{ t.engine }}">
{%- if t.engine != 'InnoDB' %}<span style="color: tomato;">{% endif -%}
@@ -94,6 +97,7 @@ Highcharts.chart('statistics', {
<td class="number_column">{{ t.data_free|octet_size }}</td>
<td class="number_column">{{ t.free_ratio|number_format(2) }}</td>
<td class="number_column">{{ t.total_length|octet_size }}</td>
<td class="number_column">{{ t.iops|number_format(2) }}</td>
</tr>
{% endfor %}
<tr>
@@ -105,6 +109,7 @@ Highcharts.chart('statistics', {
<td class="number_column"><b>{{ total_free_size|octet_size }}</b></td>
<td class="number_column"><b>{% if total_data_size %}{{ (total_free_size / total_data_size * 100)|number_format(2) }}{% else %}0{% endif %}</b></td>
<td class="number_column"><b>{{ (total_data_size + total_index_size)|octet_size }}</b></td>
<td class="number_column"><b>{{ total_iops|number_format(2) }}</b></td>
</tr>
</table>
{{ footer() }}

View File

@@ -25,6 +25,7 @@
<th>Rows read</th>
<th>Rows changed</th>
<th>Rows changed per index</th>
<th>IOPS</th>
</tr>
<tr>
<td>{{ table.stats.TABLE_ROWS|number_format }}</td>
@@ -35,6 +36,7 @@
<td>{{ table.stats.ROWS_READ|number_format }}</td>
<td>{{ table.stats.ROWS_CHANGED|number_format }}</td>
<td>{{ table.stats.ROWS_CHANGED_X_INDEXES|number_format }}</td>
<td>{{ table.stats.iops|number_format(2) }}</td>
</tr>
</table>
</div></div>

View File

@@ -345,6 +345,7 @@ class DbTest extends TestCase {
[
"TABLE_ROWS", "AVG_ROW_LENGTH", "DATA_LENGTH", "INDEX_LENGTH",
"DATA_FREE", "ROWS_READ", "ROWS_CHANGED", "ROWS_CHANGED_X_INDEXES",
"iops",
],
array_keys($table->stats()),
'mysql-table-stats',

View File

@@ -9,13 +9,25 @@ use Gazelle\Enum\MysqlTableMode;
class MysqlInfoTest extends TestCase {
public function testDirection(): void {
$this->assertEquals(MysqlInfoOrderBy::totalLength, DB\MysqlInfo::lookupOrderby('total_length'), 'mysqlfo-orderby-totallength');
$this->assertEquals(MysqlInfoOrderBy::tableName, DB\MysqlInfo::lookupOrderby('table_name'), 'mysqlfo-orderby-tablename');
$this->assertEquals(MysqlInfoOrderBy::tableName, DB\MysqlInfo::lookupOrderby('wut'), 'mysqlfo-orderby-default');
$this->assertEquals(
MysqlInfoOrderBy::totalLength,
DB\MysqlInfo::lookupOrderby('total_length'),
'mysqlfo-orderby-totallength',
);
$this->assertEquals(
MysqlInfoOrderBy::tableName,
DB\MysqlInfo::lookupOrderby('table_name'),
'mysqlfo-orderby-tablename',
);
$this->assertEquals(
MysqlInfoOrderBy::tableName,
DB\MysqlInfo::lookupOrderby('wut'),
'mysqlfo-orderby-default',
);
}
public function testMysqlInfoColumn(): void {
$this->assertCount(9, DB\MysqlInfo::columnList(), 'myinfo-column-list');
$this->assertCount(10, DB\MysqlInfo::columnList(), 'myinfo-column-list');
}
public function testMysqlInfoList(): void {