131 lines
4.5 KiB
C++
131 lines
4.5 KiB
C++
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
module Crafter.Debug:Timeline_impl;
|
||
|
|
import std;
|
||
|
|
import Crafter.Debug.Core;
|
||
|
|
import :Registry;
|
||
|
|
import :Timeline;
|
||
|
|
|
||
|
|
using namespace Crafter::Debug;
|
||
|
|
using namespace Crafter::Debug::Detail;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
constexpr std::size_t capacity = 8192;
|
||
|
|
|
||
|
|
bool Stored(std::uint64_t failures) {
|
||
|
|
return failures <= 10 || std::has_single_bit(failures);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string_view Crafter::Debug::EntryKindName(EntryKind kind) {
|
||
|
|
switch (kind) {
|
||
|
|
case EntryKind::Log: return "log";
|
||
|
|
case EntryKind::Warn: return "warn";
|
||
|
|
case EntryKind::Error: return "error";
|
||
|
|
case EntryKind::Check: return "check";
|
||
|
|
case EntryKind::Event: return "event";
|
||
|
|
case EntryKind::Agent: return "agent";
|
||
|
|
case EntryKind::Pause: return "pause";
|
||
|
|
case EntryKind::Stdio: return "stdio";
|
||
|
|
}
|
||
|
|
return "unknown";
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint8_t Crafter::Debug::Detail::Level(EntryKind kind) {
|
||
|
|
switch (kind) {
|
||
|
|
case EntryKind::Warn: return 1;
|
||
|
|
case EntryKind::Error:
|
||
|
|
case EntryKind::Check: return 2;
|
||
|
|
default: return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TimelineEntry Crafter::Debug::Detail::MakeEntry(EntryKind kind, std::string_view category, std::string message, const std::source_location& location, Value payload) {
|
||
|
|
TimelineEntry entry;
|
||
|
|
entry.kind = kind;
|
||
|
|
entry.category = std::string(category);
|
||
|
|
entry.message = std::move(message);
|
||
|
|
entry.file = location.file_name();
|
||
|
|
entry.line = location.line();
|
||
|
|
entry.payload = std::move(payload);
|
||
|
|
return entry;
|
||
|
|
}
|
||
|
|
|
||
|
|
Timeline& Timeline::Instance() {
|
||
|
|
static Timeline timeline;
|
||
|
|
return timeline;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint64_t Timeline::Append(TimelineEntry entry) {
|
||
|
|
entry.frame = CurrentFrame();
|
||
|
|
{
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
entry.seconds = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||
|
|
entry.sequence = nextSequence++;
|
||
|
|
entries.push_back(entry);
|
||
|
|
if (entries.size() > capacity) entries.pop_front();
|
||
|
|
}
|
||
|
|
Notify(entry);
|
||
|
|
return entry.sequence;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Timeline::RecordCheck(std::string_view category, std::string message, const std::source_location& location) {
|
||
|
|
TimelineEntry entry = MakeEntry(EntryKind::Check, category, std::move(message), location);
|
||
|
|
entry.frame = CurrentFrame();
|
||
|
|
{
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
entry.seconds = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
|
||
|
|
SiteCount& site = checks[{ entry.file, entry.line }];
|
||
|
|
site.category = entry.category;
|
||
|
|
entry.count = ++site.failures;
|
||
|
|
if (Stored(entry.count)) {
|
||
|
|
entry.sequence = nextSequence++;
|
||
|
|
entries.push_back(entry);
|
||
|
|
if (entries.size() > capacity) entries.pop_front();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Notify(entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<TimelineEntry> Timeline::Query(const TimelineQuery& query) const {
|
||
|
|
std::vector<TimelineEntry> matched;
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
for (const TimelineEntry& entry : entries) {
|
||
|
|
if (entry.sequence <= query.afterSequence) continue;
|
||
|
|
if (query.sinceFrame && entry.frame < *query.sinceFrame) continue;
|
||
|
|
if (Level(entry.kind) < query.minLevel) continue;
|
||
|
|
if (!query.kinds.empty() && std::ranges::find(query.kinds, entry.kind) == query.kinds.end()) continue;
|
||
|
|
if (!query.category.empty() && !GlobMatch(query.category, entry.category)) continue;
|
||
|
|
if (!query.grep.empty() && entry.message.find(query.grep) == std::string::npos && entry.category.find(query.grep) == std::string::npos) continue;
|
||
|
|
matched.push_back(entry);
|
||
|
|
}
|
||
|
|
if (query.last != 0 && matched.size() > query.last) matched.erase(matched.begin(), matched.end() - static_cast<std::ptrdiff_t>(query.last));
|
||
|
|
return matched;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::uint64_t Timeline::LastSequence() const {
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
return nextSequence - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<CheckSite> Timeline::CheckSites() const {
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
std::vector<CheckSite> sites;
|
||
|
|
for (const auto& [site, count] : checks) sites.push_back({ site.first, site.second, count.category, count.failures });
|
||
|
|
return sites;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Timeline::SetListener(std::function<void(const TimelineEntry&)> callback) {
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
listener = callback ? std::make_shared<const std::function<void(const TimelineEntry&)>>(std::move(callback)) : nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Timeline::Notify(const TimelineEntry& entry) const {
|
||
|
|
std::shared_ptr<const std::function<void(const TimelineEntry&)>> current;
|
||
|
|
{
|
||
|
|
std::lock_guard lock(mutex);
|
||
|
|
current = listener;
|
||
|
|
}
|
||
|
|
if (current) (*current)(entry);
|
||
|
|
}
|