Files
ppsspp/Common/Log/LogManager.h
T
Codex ac0585439d Logging: make ring snapshot payload accesses atomic
Keep the bounded nonblocking sequence protocol, but transfer text through retro_atomic words. Readers may discard overlapping writes without invoking a plain-memory data race. Use unsigned sequence arithmetic at wraparound. This adds bounded atomic word work only to ring logging.
2026-10-09 14:55:58 +02:00

228 lines
6.9 KiB
C++

// Copyright (C) 2003 Dolphin 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 SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#pragma once
#include "ppsspp_config.h"
#include <vector>
#include <cstdio>
#include "Common/Common.h"
#include "Common/CommonFuncs.h"
#include "Common/Log.h"
#include "Common/File/Path.h"
#include "Common/Thread/ParkingLot.h"
#define MAX_MESSAGES 8000
// The running PSP thread, for log headers. Thread local: only the thread running the PSP sets it.
extern thread_local const char *hleCurrentThreadName;
// Struct that listeners can output how they want. For example, on Android we don't want to add
// timestamp or write the level as a string, those already exist.
struct LogMessage {
char timestamp[16];
char header[64]; // Filename/thread/etc. in front.
LogLevel level;
const char *log;
std::string msg; // The actual log message.
};
enum class LogOutput {
Stdio = (1 << 0), // Also android log.
DebugString = (1 << 1),
RingBuffer = (1 << 2),
File = (1 << 3),
Printf = (1 << 5),
ExternalCallback = (1 << 6),
};
ENUM_CLASS_BITOPS(LogOutput);
// Lock-free ring of recent messages. Writers claim a slot with fetch_add and
// guard it with a per-slot sequence (odd while being written). Readers that
// catch a slot mid-write get an empty string instead of waiting.
class RingbufferLog {
public:
RingbufferLog();
void Log(const LogMessage &msg);
int GetCount() const;
std::string TextAt(int i) const;
LogLevel LevelAt(int i) const;
void Clear();
private:
enum { MAX_LOGS = 256, MAX_TEXT = 512, TEXT_WORDS = MAX_TEXT / sizeof(int) };
static_assert(MAX_TEXT % sizeof(int) == 0, "Log text must fit whole atomic words");
struct Slot {
retro_atomic_int_t seq;
retro_atomic_int_t level;
// Sequence validation alone cannot legalize concurrent plain-byte access.
retro_atomic_int_t text[TEXT_WORDS];
};
bool Read(int i, std::string *text, LogLevel *level) const;
Slot slots_[MAX_LOGS];
retro_atomic_int_t head_;
retro_atomic_int_t count_;
};
class Section;
typedef void (*LogCallback)(const LogMessage &message, void *userdata);
extern bool *g_bLogEnabledSetting;
class LogManager {
public:
LogManager();
~LogManager();
void SetOutputsEnabled(LogOutput outputs);
LogOutput GetOutputsEnabled() const {
return (LogOutput)retro_atomic_load_relaxed_int(const_cast<retro_atomic_int_t *>(&outputs_));
}
void EnableOutput(LogOutput output) {
retro_atomic_fetch_or_int(&outputs_, (int)output);
if (output & LogOutput::File)
SetFileLogPath(logFilename_);
}
void DisableOutput(LogOutput output) {
retro_atomic_fetch_and_int(&outputs_, ~(int)output);
}
void EnableOutput(LogOutput output, bool enabled) {
if (enabled) {
EnableOutput(output);
} else {
DisableOutput(output);
}
}
static u32 GetMaxLevel() { return (u32)MAX_LOGLEVEL; }
static int GetNumChannels() { return (int)Log::NUMBER_OF_LOGS; }
void LogLine(LogLevel level, Log type,
const char *file, int line, const char *fmt, va_list args);
LogChannel *GetLogChannel(Log type) {
return &g_log[(size_t)type];
}
void SetLogLevel(Log type, LogLevel level) {
g_log[(size_t)type].SetLevel(level);
}
void SetAllLogLevels(LogLevel level) {
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; ++i) {
g_log[i].SetLevel(level);
}
}
void SetAllLogEnable(bool enable) {
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; ++i) {
g_log[i].SetEnabled(enable);
}
}
void SetEnabled(Log type, bool enable) {
g_log[(size_t)type].SetEnabled(enable);
}
LogLevel GetLogLevel(Log type) {
return g_log[(size_t)type].Level();
}
const RingbufferLog &GetRingbuffer() const {
return ringLog_;
}
void Init(bool *enabledSetting, bool headless = false);
void Shutdown();
// A list rather than a single slot, because the WebSocket debugger registers one callback per
// *connection* (LogBroadcaster is a local in the per-connection handler) and more than one
// client can be attached at once - the bundled JS debugger in a browser alongside Tools/wsdbg,
// say. With a single slot the second connection silently took the log stream away from the
// first, and then the first one to disconnect cleared the slot and stopped delivery to the
// other as well.
// Returns a handle to hand back to RemoveExternalLogCallback(), or -1 if it wasn't added.
// Add/Remove/SetFileLogPath/Shutdown are control-side calls: one thread at a time.
// Logging itself may happen from any thread, concurrently with them.
int AddExternalLogCallback(LogCallback callback, void *userdata);
void RemoveExternalLogCallback(int handle);
void SetFileLogPath(const Path &filename);
const Path &GetLogFilePath() const { return logFilename_; }
void SaveConfig(Section *section);
void LoadConfig(const Section *section);
// Channel level/enabled changes made through the WebSocket debugger (log.channel.set) are
// meant as temporary, session-only diagnostic tweaks - call this so SaveConfig() skips
// writing (and thus permanently overwriting) the user's real saved settings with them.
void NotifyChannelsChangedByDebugger() { channelsChangedByDebugger_ = true; }
static const char *GetLogTypeName(Log type);
static u32 GetLevelColor(LogLevel level) {
switch (level) {
case LogLevel::LDEBUG: return 0xE0E0E0;
case LogLevel::LWARNING: return 0x50FFFF;
case LogLevel::LERROR: return 0x5050FF;
case LogLevel::LNOTICE: return 0x30FF30;
case LogLevel::LINFO: return 0xFFFF60;
case LogLevel::LVERBOSE:
default: return 0xC0C0C0;
}
}
private:
// Prevent copies.
LogManager(const LogManager &) = delete;
void operator=(const LogManager &) = delete;
bool initialized_ = false;
bool channelsChangedByDebugger_ = false;
// Stdio logging
void StdioLog(const LogMessage &message);
bool stdioUseColor_ = true;
retro_atomic_int_t outputs_;
// File logging. fp_ is published to loggers, closed only after a drain.
void CloseLogFile();
retro_atomic_ptr_t fp_;
bool logFileOpenFailed_ = false;
Path logFilename_;
// Ring buffer
RingbufferLog ringLog_;
// Callbacks: published entries, freed only after a drain.
struct ExternalCallbackEntry {
LogCallback callback;
void *userdata;
};
enum { MAX_EXTERNAL_CALLBACKS = 8 };
retro_atomic_ptr_t externalCallbacks_[MAX_EXTERNAL_CALLBACKS];
retro_atomic_int_t externalCount_;
// Loggers hold this while touching fp_ or a callback entry.
ReaderGate gate_;
};
extern LogManager g_logManager;