diff --git a/dDashboardServer/routes/UgcRoutes.cpp b/dDashboardServer/routes/UgcRoutes.cpp index 54f5578b8..6754a7269 100644 --- a/dDashboardServer/routes/UgcRoutes.cpp +++ b/dDashboardServer/routes/UgcRoutes.cpp @@ -191,7 +191,7 @@ namespace { return { { "id", std::to_string(entry.id) }, { "characterId", std::to_string(entry.characterId) }, { "characterName", entry.characterName }, { "accountId", entry.accountId }, { "accountName", entry.accountName }, { "state", IUgc::ProcessStateName(entry.state) }, { "attempts", entry.attempts }, { "processedAt", entry.processedAt }, { "error", entry.error }, { "bakeAo", entry.bakeAo }, { "processAfter", entry.processAfter }, - { "detail", entry.detail }, { "bricks", entry.bricks }, { "triangles", entry.triangles }, { "processMs", entry.processMs } }; + { "detail", entry.detail }, { "bricks", entry.bricks }, { "triangles", entry.triangles }, { "processMs", entry.processMs }, { "processCpuMs", entry.processCpuMs }, { "processMemoryKb", entry.processMemoryKb } }; } // Web thread: every car and rocket build (the assemblies are made from them) diff --git a/dDashboardServer/static/js/ugc.js b/dDashboardServer/static/js/ugc.js index 57395f42a..ecb758770 100644 --- a/dDashboardServer/static/js/ugc.js +++ b/dDashboardServer/static/js/ugc.js @@ -89,16 +89,26 @@ return ''; } function moduleNames(i) { return (i.moduleList || []).map(function (m) { return m.name || m.lot; }).join(', '); } - // How long a make took: "850 ms", "12.4 s", "2 min 5 s" + // A duration in the largest units that fit: "850 ms", "12.4 s", "2 min 5 s", "1 h 4 min", "2 d 3 h" function duration(ms) { + ms = Math.round(ms); if (ms < 1000) return ms + ' ms'; - if (ms < 60000) return (ms / 1000).toFixed(1) + ' s'; - return Math.floor(ms / 60000) + ' min ' + Math.round((ms % 60000) / 1000) + ' s'; + if (ms < 60000) return (ms / 1000).toFixed(ms < 10000 ? 2 : 1) + ' s'; + var s = Math.round(ms / 1000), m = Math.floor(s / 60), h = Math.floor(m / 60), d = Math.floor(h / 24); + if (d) return d + ' d' + (h % 24 ? ' ' + (h % 24) + ' h' : ''); + if (h) return h + ' h' + (m % 60 ? ' ' + (m % 60) + ' min' : ''); + return m + ' min' + (s % 60 ? ' ' + (s % 60) + ' s' : ''); } - // When it was made and how long that took (the time is only known for makes since it was recorded) + function megabytes(kb) { return kb >= 1048576 ? (kb / 1048576).toFixed(1) + ' GB' : kb >= 1024 ? Math.round(kb / 1024) + ' MB' : kb + ' KB'; } + // What the last make cost: its time, the worker's CPU time and the memory the UGC server estimated for it + function costText(i) { + if (!i.processMs) return ''; + return 'took ' + duration(i.processMs) + (i.processCpuMs ? ', CPU ' + duration(i.processCpuMs) : '') + (i.processMemoryKb ? ', ~' + megabytes(i.processMemoryKb) + ' RAM (est.)' : ''); + } + // When it was made and what that cost (the cost is only known for makes since it was recorded) function madeText(i) { if (!i.processedAt) return ''; - return fmt.unix(i.processedAt) + (i.processMs ? ' \u00b7 took ' + duration(i.processMs) : ''); + return fmt.unix(i.processedAt) + (costText(i) ? ' \u00b7 ' + costText(i) : ''); } function waitBadge(i) { return i.state === 'pending' && i.processAfter > Date.now() / 1000 ? ' after ' + esc(fmt.unix(i.processAfter)) + '' : ''; @@ -288,7 +298,7 @@ meter('Jobs\' memory', s.jobMemoryBytes || 0, l.maxMemoryBytes || 0, esc(mb(s.jobMemoryBytes || 0)) + ' estimated' + (l.maxMemoryBytes ? ' of ' + esc(mb(l.maxMemoryBytes)) : ', no limit') + (s.memoryWaits ? ', ' + esc(s.memoryWaits) + ' waits' : '')) + '' + (l.nice ? '
Workers run at priority ' + esc(l.nice) + '.
' : '') + - (last ? '
Last: ' + esc(last.kind) + ' ' + esc(last.id) + (last.ok ? ' made in ' + esc(last.ms) + ' ms' : ' failed') + + (last ? '
Last: ' + esc(last.kind) + ' ' + esc(last.id) + (last.ok ? ' made in ' + esc(duration(last.ms)) : ' failed') + (last.message ? ' (' + esc(last.message) + ')' : '') + '
' : ''); }).catch(function () { box.innerHTML = fmt.badge('Unknown', 'secondary') + ' Couldn\'t ask the dashboard for the UGC server\'s status.'; }); } diff --git a/dDatabase/GameDatabase/ITables/IUgc.h b/dDatabase/GameDatabase/ITables/IUgc.h index dd11b333f..1fb236250 100644 --- a/dDatabase/GameDatabase/ITables/IUgc.h +++ b/dDatabase/GameDatabase/ITables/IUgc.h @@ -95,8 +95,13 @@ public: // What the UGC server counted when it made a model: its bricks and the most detailed mesh's triangles virtual void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) = 0; - // How long the last successful make of a model took (process_ms), for the dashboard - virtual void SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) = 0; + // The last successful make of a model: how long it took, the worker's CPU time and the estimated memory + struct ProcessStats { + uint32_t milliseconds{}; + uint32_t cpuMilliseconds{}; + uint32_t memoryKb{}; + }; + virtual void SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) = 0; virtual std::optional GetUgcProcessInfo(const LWOOBJID id) = 0; diff --git a/dDatabase/GameDatabase/ITables/IUgcLookup.h b/dDatabase/GameDatabase/ITables/IUgcLookup.h index e1a3117fa..f0228a01e 100644 --- a/dDatabase/GameDatabase/ITables/IUgcLookup.h +++ b/dDatabase/GameDatabase/ITables/IUgcLookup.h @@ -37,6 +37,8 @@ public: uint32_t bricks{}; // models: counted by the UGC server when it made them (0: not yet) uint32_t triangles{}; // models: of the made mesh's most detailed level (0: not yet) uint32_t processMs{}; // how long the last successful make took (0: not made, or made before it was timed) + uint32_t processCpuMs{}; // the worker thread's CPU time for it + uint32_t processMemoryKb{}; // the memory the UGC server estimated for it (not measured) }; // What SearchUgc matches. A number (when set) is matched against ids; text against names diff --git a/dDatabase/GameDatabase/ITables/IUgcModularBuild.h b/dDatabase/GameDatabase/ITables/IUgcModularBuild.h index b04c3ff10..937b28f4e 100644 --- a/dDatabase/GameDatabase/ITables/IUgcModularBuild.h +++ b/dDatabase/GameDatabase/ITables/IUgcModularBuild.h @@ -32,8 +32,8 @@ public: // The combination of modules a build was made as (UgcModularKey::StorageId of its modules), whose files it shares virtual void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) = 0; - // How long the last successful make of a build's icon took (process_ms), for the dashboard - virtual void SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) = 0; + // The last successful make of a build's icon (see IUgc::ProcessStats) + virtual void SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) = 0; // Up to `limit` builds whose combination isn't recorded yet (combination_id 0): their ids and modules virtual std::vector GetModularBuildsWithoutCombination(const uint32_t limit) = 0; diff --git a/dDatabase/GameDatabase/ITables/UgcLookupSql.h b/dDatabase/GameDatabase/ITables/UgcLookupSql.h index 06084d68e..d259db041 100644 --- a/dDatabase/GameDatabase/ITables/UgcLookupSql.h +++ b/dDatabase/GameDatabase/ITables/UgcLookupSql.h @@ -31,10 +31,10 @@ namespace UgcLookupSql { return (modular ? "SELECT b.ugc_id AS id, b.character_id, c.name AS character_name, COALESCE(c.account_id, 0) AS account_id, a.name AS account_name, " "b.is_optimized, b.process_error, b.ldf_config AS detail, b.process_attempts, b.processed_at, 0 AS process_after, 0 AS bake_ao, " - "0 AS brick_count, 0 AS triangle_count, b.process_ms " + "0 AS brick_count, 0 AS triangle_count, b.process_ms, b.process_cpu_ms, b.process_memory_kb " : "SELECT u.id, u.character_id, c.name AS character_name, u.account_id, a.name AS account_name, " "u.is_optimized, u.process_error, u.filename AS detail, u.process_attempts, u.processed_at, u.process_after, u.bake_ao, " - "u.brick_count, u.triangle_count, u.process_ms ") + From(modular); + "u.brick_count, u.triangle_count, u.process_ms, u.process_cpu_ms, u.process_memory_kb ") + From(modular); } // Whether a search has anything to match (else a list is of everything) diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index fea0b6bc8..d22bf8467 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -432,8 +432,8 @@ public: void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override; std::optional GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override; void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override; - void SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) override; - void SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) override; + void SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) override; + void SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) override; std::vector GetModularBuildsWithoutCombination(const uint32_t limit) override; std::optional GetUgcProcessInfo(const LWOOBJID id) override; uint64_t ResetUgcModelProcessing(const std::optional id, const bool failedOnly) override; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index cbe17cc43..38e8a9b65 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -195,6 +195,6 @@ std::optional MySQLDatabase::GetUgcFileChecksum(const LWOOBJ return IUgc::FileChecksum{ std::string(result->getString("md5").c_str()), static_cast(result->getUInt("size")) }; } -void MySQLDatabase::SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) { - ExecuteUpdate("UPDATE ugc SET process_ms = ? WHERE id = ?;", milliseconds, id); +void MySQLDatabase::SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) { + ExecuteUpdate("UPDATE ugc SET process_ms = ?, process_cpu_ms = ?, process_memory_kb = ? WHERE id = ?;", stats.milliseconds, stats.cpuMilliseconds, stats.memoryKb, id); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/UgcLookup.cpp b/dDatabase/GameDatabase/MySQL/Tables/UgcLookup.cpp index 2a5286d21..99403b4a3 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/UgcLookup.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/UgcLookup.cpp @@ -21,6 +21,8 @@ namespace { entry.bricks = static_cast(result->getInt64("brick_count")); entry.triangles = static_cast(result->getInt64("triangle_count")); entry.processMs = static_cast(result->getInt64("process_ms")); + entry.processCpuMs = static_cast(result->getInt64("process_cpu_ms")); + entry.processMemoryKb = static_cast(result->getInt64("process_memory_kb")); return entry; } } diff --git a/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp b/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp index 403bdde00..ed427d366 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp @@ -120,6 +120,6 @@ std::vector MySQLDatabase::GetModularBuildsWitho return builds; } -void MySQLDatabase::SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) { - ExecuteUpdate("UPDATE ugc_modular_build SET process_ms = ? WHERE ugc_id = ?;", milliseconds, id); +void MySQLDatabase::SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) { + ExecuteUpdate("UPDATE ugc_modular_build SET process_ms = ?, process_cpu_ms = ?, process_memory_kb = ? WHERE ugc_id = ?;", stats.milliseconds, stats.cpuMilliseconds, stats.memoryKb, id); } diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 981075b0f..a7e609532 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -416,8 +416,8 @@ public: void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override; std::optional GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override; void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override; - void SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) override; - void SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) override; + void SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) override; + void SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) override; std::vector GetModularBuildsWithoutCombination(const uint32_t limit) override; std::optional GetUgcProcessInfo(const LWOOBJID id) override; uint64_t ResetUgcModelProcessing(const std::optional id, const bool failedOnly) override; diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index 72ec262af..c3de4f3ec 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -198,6 +198,6 @@ std::optional SQLiteDatabase::GetUgcFileChecksum(const LWOOB return IUgc::FileChecksum{ std::string(result.getStringField("md5", "")), static_cast(result.getInt64Field("size")) }; } -void SQLiteDatabase::SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) { - ExecuteUpdate("UPDATE ugc SET process_ms = ? WHERE id = ?;", milliseconds, id); +void SQLiteDatabase::SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) { + ExecuteUpdate("UPDATE ugc SET process_ms = ?, process_cpu_ms = ?, process_memory_kb = ? WHERE id = ?;", stats.milliseconds, stats.cpuMilliseconds, stats.memoryKb, id); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/UgcLookup.cpp b/dDatabase/GameDatabase/SQLite/Tables/UgcLookup.cpp index a9209767a..397a7644e 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/UgcLookup.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/UgcLookup.cpp @@ -21,6 +21,8 @@ namespace { entry.bricks = static_cast(result.getInt64Field("brick_count")); entry.triangles = static_cast(result.getInt64Field("triangle_count")); entry.processMs = static_cast(result.getInt64Field("process_ms")); + entry.processCpuMs = static_cast(result.getInt64Field("process_cpu_ms")); + entry.processMemoryKb = static_cast(result.getInt64Field("process_memory_kb")); return entry; } } diff --git a/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp index 076816270..9e70d662e 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp @@ -127,6 +127,6 @@ std::vector SQLiteDatabase::GetModularBuildsWith return builds; } -void SQLiteDatabase::SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) { - ExecuteUpdate("UPDATE ugc_modular_build SET process_ms = ? WHERE ugc_id = ?;", milliseconds, id); +void SQLiteDatabase::SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) { + ExecuteUpdate("UPDATE ugc_modular_build SET process_ms = ?, process_cpu_ms = ?, process_memory_kb = ? WHERE ugc_id = ?;", stats.milliseconds, stats.cpuMilliseconds, stats.memoryKb, id); } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 5ff358949..a4a3c1b55 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -414,8 +414,8 @@ class TestSQLDatabase : public GameDatabase { void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override {} std::optional GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override { return std::nullopt; } void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override {} - void SetUgcModelProcessMs(const LWOOBJID id, const uint32_t milliseconds) override {} - void SetModularBuildProcessMs(const LWOOBJID id, const uint32_t milliseconds) override {} + void SetUgcModelProcessStats(const LWOOBJID id, const ProcessStats& stats) override {} + void SetModularBuildProcessStats(const LWOOBJID id, const IUgc::ProcessStats& stats) override {} std::vector GetModularBuildsWithoutCombination(const uint32_t limit) override { return {}; } std::optional GetUgcProcessInfo(const LWOOBJID id) override { return {}; } uint64_t ResetUgcModelProcessing(const std::optional id, const bool failedOnly) override { return 0; } diff --git a/dUgcServer/UgcProcessor.cpp b/dUgcServer/UgcProcessor.cpp index feac3a83b..aab21f790 100644 --- a/dUgcServer/UgcProcessor.cpp +++ b/dUgcServer/UgcProcessor.cpp @@ -221,7 +221,9 @@ void UgcProcessor::Worker() { continue; } const auto start = std::chrono::steady_clock::now(); + const double cpuStart = UgcThrottle::ThreadCpuSeconds(); Done done{ job.kind, job.id, job.attempts }; + done.memoryEstimate = job.memory; done.iconOnly = job.iconOnly; try { if (job.iconOnly) { @@ -262,6 +264,7 @@ void UgcProcessor::Worker() { } done.outcome.files.clear(); done.milliseconds = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + done.cpuMilliseconds = std::max(0.0, UgcThrottle::ThreadCpuSeconds() - cpuStart) * 1000.0; UgcThrottle::Checkpoint(); { @@ -367,6 +370,8 @@ void UgcProcessor::Poll() { } void UgcProcessor::Record(const Done& done) { + // What the make cost (wall time, the worker's CPU time, the estimated memory), for the dashboard + const IUgc::ProcessStats cost{ static_cast(done.milliseconds), static_cast(done.cpuMilliseconds), static_cast(done.memoryEstimate / 1024) }; auto error = done.outcome.error.substr(0, MAX_ERROR_LENGTH); const auto attempts = done.attempts + 1; const auto state = done.outcome.ok ? IUgc::eProcessState::DONE @@ -374,7 +379,7 @@ void UgcProcessor::Record(const Done& done) { : attempts >= m_Config.maxAttempts ? IUgc::eProcessState::FAILED : IUgc::eProcessState::PENDING; if (done.kind == Kind::MODEL) { Database::Get()->SetUgcModelProcessed(done.id, state, attempts, error, done.outcome.ok && done.outcome.aoBaked); - if (done.outcome.ok) Database::Get()->SetUgcModelProcessMs(done.id, static_cast(done.milliseconds)); + if (done.outcome.ok) Database::Get()->SetUgcModelProcessStats(done.id, cost); // What it counted (stats.json), for sorting on the dashboard const auto stats = done.outcome.ok && !done.outcome.stats.empty() ? nlohmann::json::parse(done.outcome.stats, nullptr, false) : nlohmann::json(); if (stats.is_object()) { @@ -384,7 +389,7 @@ void UgcProcessor::Record(const Done& done) { } } else { Database::Get()->SetModularBuildProcessed(done.id, state, attempts, error); - if (done.outcome.ok) Database::Get()->SetModularBuildProcessMs(done.id, static_cast(done.milliseconds)); + if (done.outcome.ok) Database::Get()->SetModularBuildProcessStats(done.id, cost); // Which combination's files it shares, for the worlds' manifest answers if (const auto combo = m_ComboOf.find(done.id); combo != m_ComboOf.end()) Database::Get()->SetModularBuildCombination(done.id, combo->second); } diff --git a/dUgcServer/UgcProcessor.h b/dUgcServer/UgcProcessor.h index 23582c2e2..e60a1ae7e 100644 --- a/dUgcServer/UgcProcessor.h +++ b/dUgcServer/UgcProcessor.h @@ -173,6 +173,8 @@ private: UgcJobs::Outcome outcome; uint64_t bytes{}; double milliseconds{}; + double cpuMilliseconds{}; // the worker thread's CPU time for it + uint64_t memoryEstimate{}; // the bytes it was estimated to need (the memory budget's figure) bool iconOnly{}; std::vector checksums; // of the files written that the client downloads as sd0 }; diff --git a/migrations/dlu/mysql/91_ugc_process_diagnostics.sql b/migrations/dlu/mysql/91_ugc_process_diagnostics.sql new file mode 100644 index 000000000..bc45bd814 --- /dev/null +++ b/migrations/dlu/mysql/91_ugc_process_diagnostics.sql @@ -0,0 +1,25 @@ +/* UGC server: process_cpu_ms is the CPU time the worker thread spent on the last successful make, process_memory_kb + the memory the UGC server estimated for it (what its memory budget counts; not measured), for the dashboard. */ +SET @dlu_column = (SELECT IF(COUNT(*) = 0, 'ALTER TABLE ugc ADD COLUMN process_cpu_ms INT UNSIGNED NOT NULL DEFAULT 0', 'DO 0') FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'ugc' AND column_name = 'process_cpu_ms'); +PREPARE dlu_column_stmt FROM @dlu_column; +EXECUTE dlu_column_stmt; +DEALLOCATE PREPARE dlu_column_stmt; + +SET @dlu_column = (SELECT IF(COUNT(*) = 0, 'ALTER TABLE ugc ADD COLUMN process_memory_kb INT UNSIGNED NOT NULL DEFAULT 0', 'DO 0') FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'ugc' AND column_name = 'process_memory_kb'); +PREPARE dlu_column_stmt FROM @dlu_column; +EXECUTE dlu_column_stmt; +DEALLOCATE PREPARE dlu_column_stmt; + +SET @dlu_column = (SELECT IF(COUNT(*) = 0, 'ALTER TABLE ugc_modular_build ADD COLUMN process_cpu_ms INT UNSIGNED NOT NULL DEFAULT 0', 'DO 0') FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'ugc_modular_build' AND column_name = 'process_cpu_ms'); +PREPARE dlu_column_stmt FROM @dlu_column; +EXECUTE dlu_column_stmt; +DEALLOCATE PREPARE dlu_column_stmt; + +SET @dlu_column = (SELECT IF(COUNT(*) = 0, 'ALTER TABLE ugc_modular_build ADD COLUMN process_memory_kb INT UNSIGNED NOT NULL DEFAULT 0', 'DO 0') FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'ugc_modular_build' AND column_name = 'process_memory_kb'); +PREPARE dlu_column_stmt FROM @dlu_column; +EXECUTE dlu_column_stmt; +DEALLOCATE PREPARE dlu_column_stmt; diff --git a/migrations/dlu/sqlite/74_ugc_process_diagnostics.sql b/migrations/dlu/sqlite/74_ugc_process_diagnostics.sql new file mode 100644 index 000000000..d04e1a1b8 --- /dev/null +++ b/migrations/dlu/sqlite/74_ugc_process_diagnostics.sql @@ -0,0 +1,5 @@ +/* UGC server: the CPU time and estimated memory of each model's and build's last make. See the MySQL migration. */ +ALTER TABLE ugc ADD COLUMN process_cpu_ms INTEGER NOT NULL DEFAULT 0; +ALTER TABLE ugc ADD COLUMN process_memory_kb INTEGER NOT NULL DEFAULT 0; +ALTER TABLE ugc_modular_build ADD COLUMN process_cpu_ms INTEGER NOT NULL DEFAULT 0; +ALTER TABLE ugc_modular_build ADD COLUMN process_memory_kb INTEGER NOT NULL DEFAULT 0; diff --git a/tests/dWebTests/UgcLookupTests.cpp b/tests/dWebTests/UgcLookupTests.cpp index 6c584d6ac..0f0bbe07d 100644 --- a/tests/dWebTests/UgcLookupTests.cpp +++ b/tests/dWebTests/UgcLookupTests.cpp @@ -105,9 +105,9 @@ namespace { "CREATE TABLE charinfo (id BIGINT PRIMARY KEY, account_id INTEGER, name TEXT);" "CREATE TABLE properties (id BIGINT PRIMARY KEY, owner_id BIGINT, name TEXT, zone_id INTEGER);" "CREATE TABLE ugc (id INTEGER PRIMARY KEY, account_id INTEGER, character_id BIGINT, is_optimized INTEGER, filename TEXT, process_error TEXT DEFAULT '', " - "process_attempts INTEGER DEFAULT 0, processed_at BIGINT DEFAULT 0, process_after BIGINT DEFAULT 0, bake_ao INTEGER DEFAULT 0, brick_count INTEGER DEFAULT 0, triangle_count INTEGER DEFAULT 0, process_ms INTEGER DEFAULT 0);" + "process_attempts INTEGER DEFAULT 0, processed_at BIGINT DEFAULT 0, process_after BIGINT DEFAULT 0, bake_ao INTEGER DEFAULT 0, brick_count INTEGER DEFAULT 0, triangle_count INTEGER DEFAULT 0, process_ms INTEGER DEFAULT 0, process_cpu_ms INTEGER DEFAULT 0, process_memory_kb INTEGER DEFAULT 0);" "CREATE TABLE ugc_modular_build (ugc_id BIGINT PRIMARY KEY, character_id BIGINT, ldf_config TEXT, is_optimized INTEGER DEFAULT 0, process_error TEXT DEFAULT '', " - "process_attempts INTEGER DEFAULT 0, processed_at BIGINT DEFAULT 0, process_ms INTEGER DEFAULT 0);" + "process_attempts INTEGER DEFAULT 0, processed_at BIGINT DEFAULT 0, process_ms INTEGER DEFAULT 0, process_cpu_ms INTEGER DEFAULT 0, process_memory_kb INTEGER DEFAULT 0);" "CREATE TABLE properties_contents (id BIGINT PRIMARY KEY, property_id BIGINT, ugc_id BIGINT, lot INTEGER, model_name TEXT DEFAULT '', model_description TEXT DEFAULT '');" "INSERT INTO accounts VALUES (1, 'builder'), (2, 'racer');" "INSERT INTO charinfo VALUES (10, 1, 'Bricky'), (20, 2, 'Speedy');"