mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 17:55:23 +02:00
DebuggerLogListener buffers up to 1024 log messages between polls of the
WebSocket event loop (up to 1000Hz under high activity, 60Hz otherwise -
see WebSocket.cpp). A source that logs faster than that - a log-only
breakpoint hit thousands of times in a tight loop is a real example, not
hypothetical, see docs/VSHBootInvestigation.md's Attempt 22/24 - can wrap
the ring buffer before GetMessages() ever reads the oldest entries,
silently losing them. From the client's side this was indistinguishable
from the breakpoint just not firing at all, which cost real debugging time
this session tracking down a red herring before finding the real
mechanism.
GetMessages() already detected the overflow case internally (the
`read_ + BUFFER_SIZE < count_` branch) to avoid returning garbage, but
never reported how many messages were actually lost. Now synthesizes a
warning LogMessage ("N log message(s) dropped - client polling too slow
for this volume") and prepends it to the batch whenever this happens, so
a real gap is visibly distinguishable from "this just never got logged."
Verified via UnitTest.exe all (49/49) and a live PPSSPPHeadless + wsdbg
session confirming normal (non-overflow) log relay still works
end-to-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
144 lines
4.5 KiB
C++
144 lines
4.5 KiB
C++
// Copyright (c) 2018- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include "Common/Log/LogManager.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Core/Debugger/WebSocket/LogBroadcaster.h"
|
|
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
|
|
|
class DebuggerLogListener {
|
|
public:
|
|
void Log(const LogMessage &msg) {
|
|
std::lock_guard<std::mutex> guard(lock_);
|
|
messages_[nextMessage_] = msg;
|
|
nextMessage_++;
|
|
if (nextMessage_ >= BUFFER_SIZE)
|
|
nextMessage_ -= BUFFER_SIZE;
|
|
count_++;
|
|
}
|
|
|
|
std::vector<LogMessage> GetMessages() {
|
|
std::lock_guard<std::mutex> guard(lock_);
|
|
int splitPoint;
|
|
int readCount;
|
|
// A source that logs faster than this listener gets polled (WebSocket.cpp's event loop,
|
|
// up to 1000Hz under high activity) - a log-only breakpoint hit thousands of times in a
|
|
// tight loop is a real example, see docs/VSHBootInvestigation.md - can wrap the ring
|
|
// buffer before GetMessages() ever reads the oldest entries, silently losing them. That
|
|
// used to just look like "my breakpoint only logged a few hits" from the client's side,
|
|
// indistinguishable from the breakpoint genuinely not firing - synthesize a warning
|
|
// message reporting exactly how many were lost instead of staying silent about it.
|
|
int droppedCount = 0;
|
|
if (read_ + BUFFER_SIZE < count_) {
|
|
// We'll start with our oldest then.
|
|
droppedCount = (count_ - BUFFER_SIZE) - read_;
|
|
splitPoint = nextMessage_;
|
|
readCount = Count();
|
|
} else {
|
|
splitPoint = read_;
|
|
readCount = count_ - read_;
|
|
}
|
|
|
|
read_ = count_;
|
|
|
|
std::vector<LogMessage> results;
|
|
if (droppedCount > 0) {
|
|
LogMessage dropped;
|
|
dropped.level = LogLevel::LWARNING;
|
|
dropped.log = "Debugger";
|
|
GetCurrentTimeFormatted(dropped.timestamp);
|
|
truncate_cpy(dropped.header, "LogBroadcaster: ring buffer overflow");
|
|
dropped.msg = StringFromFormat("%d log message(s) dropped - client polling too slow for this volume\n", droppedCount);
|
|
results.push_back(dropped);
|
|
}
|
|
int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE);
|
|
for (int i = splitPoint; i < splitEnd; ++i) {
|
|
results.push_back(messages_[i]);
|
|
readCount--;
|
|
}
|
|
for (int i = 0; i < readCount; ++i) {
|
|
results.push_back(messages_[i]);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
int Count() const {
|
|
return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE;
|
|
}
|
|
|
|
private:
|
|
enum { BUFFER_SIZE = 1024 };
|
|
LogMessage messages_[BUFFER_SIZE];
|
|
std::mutex lock_;
|
|
int nextMessage_ = 0;
|
|
int count_ = 0;
|
|
int read_ = 0;
|
|
};
|
|
|
|
static void BroadcastCallback(const LogMessage &message, void *userdata) {
|
|
DebuggerLogListener *listener = (DebuggerLogListener *)userdata;
|
|
listener->Log(message);
|
|
}
|
|
|
|
LogBroadcaster::LogBroadcaster() {
|
|
listener_ = new DebuggerLogListener();
|
|
g_logManager.SetExternalLogCallback(&BroadcastCallback, (void *)listener_);
|
|
g_logManager.EnableOutput(LogOutput::ExternalCallback);
|
|
}
|
|
|
|
LogBroadcaster::~LogBroadcaster() {
|
|
g_logManager.DisableOutput(LogOutput::ExternalCallback);
|
|
g_logManager.SetExternalLogCallback(nullptr, nullptr);
|
|
delete listener_;
|
|
}
|
|
|
|
struct DebuggerLogEvent {
|
|
const LogMessage &l;
|
|
|
|
operator std::string() {
|
|
JsonWriter j;
|
|
j.begin();
|
|
j.writeString("event", "log");
|
|
j.writeString("timestamp", l.timestamp);
|
|
j.writeString("header", l.header);
|
|
j.writeString("message", l.msg);
|
|
j.writeInt("level", (int)l.level);
|
|
j.writeString("channel", l.log);
|
|
j.end();
|
|
return j.str();
|
|
}
|
|
};
|
|
|
|
// Log message (log)
|
|
//
|
|
// Sent unexpectedly with these properties:
|
|
// - timestamp: string timestamp of event.
|
|
// - header: string header information about the event (including file/line.)
|
|
// - message: actual log message as a string.
|
|
// - level: number severity level (1 = highest.)
|
|
// - channel: string describing log channel / grouping.
|
|
void LogBroadcaster::Broadcast(net::WebSocketServer *ws) {
|
|
auto messages = listener_->GetMessages();
|
|
for (auto msg : messages) {
|
|
ws->Send(DebuggerLogEvent{msg});
|
|
}
|
|
}
|