mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
feat(db): measure the database's size per table and keep the samples
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>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
#include "IPropertyReputation.h"
|
||||
#include "IBbbAutosave.h"
|
||||
#include "IServerTraffic.h"
|
||||
#include "IDatabaseUsage.h"
|
||||
#include "IApiKeys.h"
|
||||
#include "IUgcLookup.h"
|
||||
#include "IPermissionGrants.h"
|
||||
@@ -71,7 +72,7 @@ class GameDatabase :
|
||||
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
|
||||
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
|
||||
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IChatFlags, public IClientSysInfo, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave, public IServerTraffic, public IApiKeys, public IUgcLookup, public IPermissionGrants, public IGuilds {
|
||||
public IBehaviors, public IUgcModularBuild, public IAuditLog, public IDashboardStats, public IAccountEmails, public IDashboardMaintenance, public IEconomyLedger, public IDashboardAdmin, public IServerConfig, public IScheduledTasks, public ICharacterSnapshots, public IAccountNotes, public IServerHealth, public IRelatedData, public IChatLog, public IChatFlags, public IClientSysInfo, public IAccountStrikes, public ISlashCommands, public IModeration, public IServerOperations, public IPlayerPositions, public IAiSuggestions, public ILiveOps, public IFeaturedProperties, public IMessageCaptures, public IContraband, public IPropertyRent, public IPropertyReputation, public IBbbAutosave, public IServerTraffic, public IApiKeys, public IUgcLookup, public IPermissionGrants, public IGuilds, public IDatabaseUsage {
|
||||
public:
|
||||
virtual ~GameDatabase() = default;
|
||||
// TODO: These should be made private.
|
||||
|
||||
57
dDatabase/GameDatabase/ITables/IDatabaseUsage.h
Normal file
57
dDatabase/GameDatabase/ITables/IDatabaseUsage.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef __IDATABASEUSAGE__H__
|
||||
#define __IDATABASEUSAGE__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* How much room the game database takes, measured by the dashboard's Database usage task (hourly by default) and
|
||||
* kept as history for database_usage_days: the whole database and every table's data, indexes and rows.
|
||||
*/
|
||||
class IDatabaseUsage {
|
||||
public:
|
||||
struct TableUsage {
|
||||
std::string name;
|
||||
uint64_t dataBytes{};
|
||||
uint64_t indexBytes{};
|
||||
uint64_t rows{}; // MySQL/MariaDB: the server's estimate (information_schema); SQLite: counted
|
||||
};
|
||||
|
||||
// The database as it is now
|
||||
struct Measurement {
|
||||
std::vector<TableUsage> tables; // by name
|
||||
uint64_t totalBytes{}; // SQLite: the file's pages; MySQL: every table's data and indexes
|
||||
uint64_t freeBytes{}; // allocated but unused: SQLite free pages, MySQL data_free (both reusable)
|
||||
std::string location; // SQLite: the database file; MySQL: the server's data directory (@@datadir)
|
||||
bool sizesKnown{ true }; // false: SQLite without the dbstat table, so only the totals and rows are known
|
||||
};
|
||||
|
||||
// Reads sizes only; slow on a big SQLite database (counts every table's rows), so run it in the background
|
||||
virtual Measurement MeasureDatabaseUsage() = 0;
|
||||
|
||||
// One sample of the whole database
|
||||
struct UsageTotals {
|
||||
int64_t time{};
|
||||
uint64_t totalBytes{};
|
||||
uint64_t freeBytes{};
|
||||
uint64_t dataBytes{}; // the tables'
|
||||
uint64_t indexBytes{};
|
||||
uint64_t rows{};
|
||||
uint64_t diskFree{}; // on the database's volume; diskTotal 0: not known (a database on another machine)
|
||||
uint64_t diskTotal{};
|
||||
};
|
||||
|
||||
virtual void InsertDatabaseUsage(const UsageTotals& totals, const std::vector<TableUsage>& tables) = 0;
|
||||
|
||||
// The samples between from and to (inclusive), oldest first
|
||||
virtual std::vector<UsageTotals> GetDatabaseUsageTotals(int64_t from, int64_t to) = 0;
|
||||
|
||||
// The tables of the sample taken at `time`, by name
|
||||
virtual std::vector<TableUsage> GetDatabaseUsageTables(int64_t time) = 0;
|
||||
|
||||
// Removes samples (and their tables) older than beforeTime; returns how many samples
|
||||
virtual uint32_t PruneDatabaseUsage(int64_t beforeTime) = 0;
|
||||
};
|
||||
|
||||
#endif //!__IDATABASEUSAGE__H__
|
||||
@@ -220,6 +220,12 @@ public:
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override;
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override;
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override;
|
||||
// IDatabaseUsage
|
||||
Measurement MeasureDatabaseUsage() override;
|
||||
void InsertDatabaseUsage(const UsageTotals& totals, const std::vector<TableUsage>& tables) override;
|
||||
std::vector<UsageTotals> GetDatabaseUsageTotals(int64_t from, int64_t to) override;
|
||||
std::vector<TableUsage> GetDatabaseUsageTables(int64_t time) override;
|
||||
uint32_t PruneDatabaseUsage(int64_t beforeTime) override;
|
||||
|
||||
// IUgcLookup
|
||||
std::vector<UgcEntry> SearchUgc(const UgcSearch& search, const uint32_t limit) override;
|
||||
|
||||
@@ -26,6 +26,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
|
||||
"PropertyReputation.cpp"
|
||||
"BbbAutosave.cpp"
|
||||
"ServerTraffic.cpp"
|
||||
"DatabaseUsage.cpp"
|
||||
"MessageCaptures.cpp"
|
||||
"ChatLog.cpp"
|
||||
"ChatFlags.cpp"
|
||||
|
||||
60
dDatabase/GameDatabase/MySQL/Tables/DatabaseUsage.cpp
Normal file
60
dDatabase/GameDatabase/MySQL/Tables/DatabaseUsage.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
IDatabaseUsage::Measurement MySQLDatabase::MeasureDatabaseUsage() {
|
||||
Measurement measurement;
|
||||
// MySQL 8 caches information_schema sizes for a day by default; this connection asks for fresh ones (MariaDB has no cache)
|
||||
{
|
||||
auto expiry = ExecuteSelect("SHOW VARIABLES LIKE 'information_schema_stats_expiry';");
|
||||
if (expiry->next()) ExecuteCustomQuery("SET SESSION information_schema_stats_expiry = 0;");
|
||||
}
|
||||
auto result = ExecuteSelect("SELECT TABLE_NAME AS name, COALESCE(DATA_LENGTH, 0) AS data_bytes, COALESCE(INDEX_LENGTH, 0) AS index_bytes, "
|
||||
"COALESCE(TABLE_ROWS, 0) AS row_count, COALESCE(DATA_FREE, 0) AS free_bytes FROM information_schema.TABLES "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME;");
|
||||
while (result->next()) {
|
||||
TableUsage table{ result->getString("name").c_str(), result->getUInt64("data_bytes"), result->getUInt64("index_bytes"), result->getUInt64("row_count") };
|
||||
measurement.totalBytes += table.dataBytes + table.indexBytes;
|
||||
measurement.freeBytes += result->getUInt64("free_bytes");
|
||||
measurement.tables.push_back(std::move(table));
|
||||
}
|
||||
auto dir = ExecuteSelect("SELECT @@datadir AS dir;");
|
||||
if (dir->next() && !dir->isNull("dir")) measurement.location = dir->getString("dir").c_str();
|
||||
return measurement;
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertDatabaseUsage(const UsageTotals& t, const std::vector<TableUsage>& tables) {
|
||||
const auto prevCommit = GetAutoCommit();
|
||||
SetAutoCommit(false);
|
||||
ExecuteInsert("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("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> MySQLDatabase::GetDatabaseUsageTotals(int64_t from, int64_t to) {
|
||||
std::vector<UsageTotals> samples;
|
||||
auto 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);
|
||||
while (r->next()) {
|
||||
samples.push_back({ r->getInt64("time"), r->getUInt64("total_bytes"), r->getUInt64("free_bytes"), r->getUInt64("data_bytes"),
|
||||
r->getUInt64("index_bytes"), r->getUInt64("row_count"), r->getUInt64("disk_free"), r->getUInt64("disk_total") });
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::vector<IDatabaseUsage::TableUsage> MySQLDatabase::GetDatabaseUsageTables(int64_t time) {
|
||||
std::vector<TableUsage> tables;
|
||||
auto r = ExecuteSelect("SELECT table_name, data_bytes, index_bytes, row_count FROM database_usage_tables WHERE time = ? ORDER BY table_name;", time);
|
||||
while (r->next()) {
|
||||
tables.push_back({ r->getString("table_name").c_str(), r->getUInt64("data_bytes"), r->getUInt64("index_bytes"), r->getUInt64("row_count") });
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
uint32_t MySQLDatabase::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));
|
||||
}
|
||||
@@ -204,6 +204,12 @@ public:
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override;
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override;
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override;
|
||||
// IDatabaseUsage
|
||||
Measurement MeasureDatabaseUsage() override;
|
||||
void InsertDatabaseUsage(const UsageTotals& totals, const std::vector<TableUsage>& tables) override;
|
||||
std::vector<UsageTotals> GetDatabaseUsageTotals(int64_t from, int64_t to) override;
|
||||
std::vector<TableUsage> GetDatabaseUsageTables(int64_t time) override;
|
||||
uint32_t PruneDatabaseUsage(int64_t beforeTime) override;
|
||||
|
||||
// IUgcLookup
|
||||
std::vector<UgcEntry> SearchUgc(const UgcSearch& search, const uint32_t limit) override;
|
||||
|
||||
@@ -26,6 +26,7 @@ set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES
|
||||
"PropertyReputation.cpp"
|
||||
"BbbAutosave.cpp"
|
||||
"ServerTraffic.cpp"
|
||||
"DatabaseUsage.cpp"
|
||||
"MessageCaptures.cpp"
|
||||
"ChatLog.cpp"
|
||||
"ChatFlags.cpp"
|
||||
|
||||
103
dDatabase/GameDatabase/SQLite/Tables/DatabaseUsage.cpp
Normal file
103
dDatabase/GameDatabase/SQLite/Tables/DatabaseUsage.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#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));
|
||||
}
|
||||
@@ -258,6 +258,11 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void InsertTrafficMinutes(const std::vector<TrafficMinute>& minutes) override {}
|
||||
std::vector<TrafficMinute> GetTrafficMinutes(int64_t from, int64_t to, int64_t bucketSeconds) override { return {}; }
|
||||
uint32_t PruneTrafficMinutes(int64_t beforeTime) override { return 0; }
|
||||
Measurement MeasureDatabaseUsage() override { return {}; }
|
||||
void InsertDatabaseUsage(const UsageTotals& totals, const std::vector<TableUsage>& tables) override {}
|
||||
std::vector<UsageTotals> GetDatabaseUsageTotals(int64_t from, int64_t to) override { return {}; }
|
||||
std::vector<TableUsage> GetDatabaseUsageTables(int64_t time) override { return {}; }
|
||||
uint32_t PruneDatabaseUsage(int64_t beforeTime) override { return 0; }
|
||||
void InsertInstanceSamples(const std::vector<InstanceSample>& samples) override {}
|
||||
std::vector<InstanceSample> GetInstanceSamples(int64_t from, int64_t to, int64_t bucketSeconds, uint32_t zoneId) override { return {}; }
|
||||
void InsertPositionSamples(const std::vector<PositionSample>& samples) override {}
|
||||
|
||||
21
migrations/dlu/mysql/105_database_usage.sql
Normal file
21
migrations/dlu/mysql/105_database_usage.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
/* How much room the game database takes, sampled by the dashboard's Database usage task and kept for
|
||||
database_usage_days. database_usage: the whole database per sample (sizes in bytes; disk_total 0 when the database's
|
||||
volume is not known); database_usage_tables: each table at that sample. row_count is the server's estimate on MySQL. */
|
||||
CREATE TABLE IF NOT EXISTS database_usage (
|
||||
time BIGINT NOT NULL PRIMARY KEY,
|
||||
total_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
free_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
data_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
index_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
row_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
disk_free BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
disk_total BIGINT UNSIGNED NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS database_usage_tables (
|
||||
time BIGINT NOT NULL,
|
||||
table_name VARCHAR(64) NOT NULL,
|
||||
data_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
index_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
row_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (time, table_name)
|
||||
);
|
||||
19
migrations/dlu/sqlite/88_database_usage.sql
Normal file
19
migrations/dlu/sqlite/88_database_usage.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
/* How much room the game database takes, per sample and per table. See the MySQL migration. */
|
||||
CREATE TABLE IF NOT EXISTS database_usage (
|
||||
time BIGINT NOT NULL PRIMARY KEY,
|
||||
total_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
free_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
data_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
index_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
row_count BIGINT NOT NULL DEFAULT 0,
|
||||
disk_free BIGINT NOT NULL DEFAULT 0,
|
||||
disk_total BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS database_usage_tables (
|
||||
time BIGINT NOT NULL,
|
||||
table_name TEXT NOT NULL,
|
||||
data_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
index_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
row_count BIGINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (time, table_name)
|
||||
);
|
||||
@@ -426,3 +426,32 @@ TEST(ClientSysInfoSqlite, ListsAcrossAccounts) {
|
||||
ASSERT_EQ(rows.size(), 1u);
|
||||
EXPECT_EQ(rows[0].buildNumber, 2600u);
|
||||
}
|
||||
|
||||
// The Database usage task's measurement and history (IDatabaseUsage) on SQLite
|
||||
TEST(DatabaseUsageSqlite, MeasuresTablesAndKeepsHistory) {
|
||||
const auto measurement = g_Db->MeasureDatabaseUsage();
|
||||
EXPECT_TRUE(measurement.sizesKnown); // built with the dbstat table
|
||||
EXPECT_GT(measurement.totalBytes, 0u);
|
||||
EXPECT_EQ(std::filesystem::weakly_canonical(measurement.location), std::filesystem::weakly_canonical(g_Path));
|
||||
const auto it = std::ranges::find(measurement.tables, std::string("database_usage"), &IDatabaseUsage::TableUsage::name);
|
||||
ASSERT_NE(it, measurement.tables.end());
|
||||
EXPECT_GT(it->dataBytes, 0u);
|
||||
EXPECT_EQ(std::ranges::count_if(measurement.tables, [](const auto& t) { return t.name.starts_with("sqlite_"); }), 0);
|
||||
|
||||
g_Db->InsertDatabaseUsage({ 1000, 500, 10, 300, 200, 7, 40, 100 }, { { "a", 100, 50, 3 }, { "b", 200, 150, 4 } });
|
||||
g_Db->InsertDatabaseUsage({ 2000, 900, 0, 600, 300, 9, 30, 100 }, { { "a", 400, 100, 5 } });
|
||||
const auto totals = g_Db->GetDatabaseUsageTotals(0, 5000);
|
||||
ASSERT_EQ(totals.size(), 2u);
|
||||
EXPECT_EQ(totals[0].time, 1000);
|
||||
EXPECT_EQ(totals[1].totalBytes, 900u);
|
||||
EXPECT_EQ(totals[1].diskFree, 30u);
|
||||
const auto tables = g_Db->GetDatabaseUsageTables(1000);
|
||||
ASSERT_EQ(tables.size(), 2u);
|
||||
EXPECT_EQ(tables[1].name, "b");
|
||||
EXPECT_EQ(tables[1].indexBytes, 150u);
|
||||
EXPECT_EQ(tables[1].rows, 4u);
|
||||
|
||||
EXPECT_EQ(g_Db->PruneDatabaseUsage(1500), 1u);
|
||||
EXPECT_TRUE(g_Db->GetDatabaseUsageTables(1000).empty());
|
||||
EXPECT_EQ(g_Db->GetDatabaseUsageTotals(0, 5000).size(), 1u);
|
||||
}
|
||||
|
||||
3
thirdparty/SQLite/CMakeLists.txt
vendored
3
thirdparty/SQLite/CMakeLists.txt
vendored
@@ -17,3 +17,6 @@ if(UNIX)
|
||||
target_compile_options(sqlite3 PRIVATE "-Wno-return-stack-address" "-Wno-uninitialized" "-Wno-deprecated-declarations")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# The dashboard measures each table's bytes (Database usage) with the dbstat table
|
||||
target_compile_definitions(sqlite3 PRIVATE SQLITE_ENABLE_DBSTAT_VTAB)
|
||||
|
||||
Reference in New Issue
Block a user