mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 15:57:13 +00:00
IDatabaseUsage: MySQL/MariaDB from information_schema.TABLES (fresh statistics on MySQL 8), SQLite from its page counts and the dbstat table (now compiled in) with counted rows. Samples go to database_usage and database_usage_tables (migrations 105 / 88). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
4.4 KiB
C++
104 lines
4.4 KiB
C++
#include "SQLiteDatabase.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
|
|
namespace {
|
|
uint64_t U64(CppSQLite3Query& result, const char* field) {
|
|
const auto value = result.getInt64Field(field);
|
|
return value > 0 ? static_cast<uint64_t>(value) : 0;
|
|
}
|
|
|
|
uint64_t Pragma(CppSQLite3Query& result) {
|
|
return result.eof() ? 0 : static_cast<uint64_t>(std::max<sqlite_int64>(result.getInt64Field(0), 0));
|
|
}
|
|
|
|
std::string Quoted(const std::string& name) {
|
|
std::string quoted = "\"";
|
|
for (const char c : name) quoted += c == '"' ? std::string("\"\"") : std::string(1, c);
|
|
return quoted + "\"";
|
|
}
|
|
}
|
|
|
|
IDatabaseUsage::Measurement SQLiteDatabase::MeasureDatabaseUsage() {
|
|
Measurement measurement;
|
|
uint64_t pageSize = 0, pages = 0, freePages = 0;
|
|
{ auto [s, r] = ExecuteSelect("PRAGMA page_size;"); pageSize = Pragma(r); }
|
|
{ auto [s, r] = ExecuteSelect("PRAGMA page_count;"); pages = Pragma(r); }
|
|
{ auto [s, r] = ExecuteSelect("PRAGMA freelist_count;"); freePages = Pragma(r); }
|
|
measurement.totalBytes = pageSize * pages;
|
|
measurement.freeBytes = pageSize * freePages;
|
|
{
|
|
auto [s, r] = ExecuteSelect("PRAGMA database_list;");
|
|
for (; !r.eof(); r.nextRow()) {
|
|
if (std::string(r.getStringField("name")) == "main") measurement.location = r.getStringField("file");
|
|
}
|
|
}
|
|
|
|
std::map<std::string, TableUsage> tables;
|
|
{
|
|
auto [s, r] = ExecuteSelect("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name;");
|
|
for (; !r.eof(); r.nextRow()) {
|
|
const std::string name = r.getStringField("name");
|
|
tables[name].name = name;
|
|
}
|
|
}
|
|
// Bytes of each table and its indexes, from the dbstat table (SQLITE_ENABLE_DBSTAT_VTAB)
|
|
try {
|
|
auto [s, r] = ExecuteSelect("SELECT m.tbl_name AS tbl, m.type AS type, SUM(d.pgsize) AS bytes FROM dbstat d JOIN sqlite_master m ON m.name = d.name "
|
|
"GROUP BY m.tbl_name, m.type;");
|
|
for (; !r.eof(); r.nextRow()) {
|
|
const auto it = tables.find(r.getStringField("tbl"));
|
|
if (it == tables.end()) continue;
|
|
if (std::string(r.getStringField("type")) == "index") it->second.indexBytes += U64(r, "bytes");
|
|
else it->second.dataBytes += U64(r, "bytes");
|
|
}
|
|
} catch (const std::exception&) {
|
|
measurement.sizesKnown = false;
|
|
}
|
|
for (auto& [name, table] : tables) {
|
|
auto [s, r] = ExecuteSelect("SELECT COUNT(*) AS n FROM " + Quoted(name) + ";");
|
|
if (!r.eof()) table.rows = U64(r, "n");
|
|
measurement.tables.push_back(table);
|
|
}
|
|
return measurement;
|
|
}
|
|
|
|
void SQLiteDatabase::InsertDatabaseUsage(const UsageTotals& t, const std::vector<TableUsage>& tables) {
|
|
const auto prevCommit = GetAutoCommit();
|
|
SetAutoCommit(false);
|
|
ExecuteInsert("INSERT OR REPLACE INTO database_usage (time, total_bytes, free_bytes, data_bytes, index_bytes, row_count, disk_free, disk_total) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?);", t.time, t.totalBytes, t.freeBytes, t.dataBytes, t.indexBytes, t.rows, t.diskFree, t.diskTotal);
|
|
for (const auto& table : tables) {
|
|
ExecuteInsert("INSERT OR REPLACE INTO database_usage_tables (time, table_name, data_bytes, index_bytes, row_count) VALUES (?, ?, ?, ?, ?);",
|
|
t.time, table.name, table.dataBytes, table.indexBytes, table.rows);
|
|
}
|
|
Commit();
|
|
SetAutoCommit(prevCommit);
|
|
}
|
|
|
|
std::vector<IDatabaseUsage::UsageTotals> SQLiteDatabase::GetDatabaseUsageTotals(int64_t from, int64_t to) {
|
|
std::vector<UsageTotals> samples;
|
|
auto [s, r] = ExecuteSelect("SELECT time, total_bytes, free_bytes, data_bytes, index_bytes, row_count, disk_free, disk_total FROM database_usage "
|
|
"WHERE time >= ? AND time <= ? ORDER BY time;", from, to);
|
|
for (; !r.eof(); r.nextRow()) {
|
|
samples.push_back({ r.getInt64Field("time"), U64(r, "total_bytes"), U64(r, "free_bytes"), U64(r, "data_bytes"), U64(r, "index_bytes"),
|
|
U64(r, "row_count"), U64(r, "disk_free"), U64(r, "disk_total") });
|
|
}
|
|
return samples;
|
|
}
|
|
|
|
std::vector<IDatabaseUsage::TableUsage> SQLiteDatabase::GetDatabaseUsageTables(int64_t time) {
|
|
std::vector<TableUsage> tables;
|
|
auto [s, r] = ExecuteSelect("SELECT table_name, data_bytes, index_bytes, row_count FROM database_usage_tables WHERE time = ? ORDER BY table_name;", time);
|
|
for (; !r.eof(); r.nextRow()) {
|
|
tables.push_back({ r.getStringField("table_name"), U64(r, "data_bytes"), U64(r, "index_bytes"), U64(r, "row_count") });
|
|
}
|
|
return tables;
|
|
}
|
|
|
|
uint32_t SQLiteDatabase::PruneDatabaseUsage(int64_t beforeTime) {
|
|
ExecuteUpdate("DELETE FROM database_usage_tables WHERE time < ?;", beforeTime);
|
|
return static_cast<uint32_t>(ExecuteUpdate("DELETE FROM database_usage WHERE time < ?;", beforeTime));
|
|
}
|