Many improvements:

* Get crash settings in assignment clients
* Allow custom crash reporting URL and token
* Fix setting system -- the client's one doesn't belong in the server
* Lots more documentation

Commit just before moving things around.
This commit is contained in:
Dale Glass 2023-07-02 21:18:09 +02:00
parent f218e54eac
commit 25755f9c88
23 changed files with 361 additions and 63 deletions

View file

@ -371,6 +371,8 @@ void AssetServer::completeSetup() {
auto& domainHandler = nodeList->getDomainHandler(); auto& domainHandler = nodeList->getDomainHandler();
const QJsonObject& settingsObject = domainHandler.getSettingsObject(); const QJsonObject& settingsObject = domainHandler.getSettingsObject();
commonParseSettingsObject(settingsObject);
static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server"; static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server";
if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) { if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) {

View file

@ -38,6 +38,7 @@
#include "AudioMixerClientData.h" #include "AudioMixerClientData.h"
#include "AvatarAudioStream.h" #include "AvatarAudioStream.h"
#include "InjectedAudioStream.h" #include "InjectedAudioStream.h"
#include "crash-handler/CrashHandler.h"
using namespace std; using namespace std;
@ -49,6 +50,7 @@ static const QString AUDIO_ENV_GROUP_KEY = "audio_env";
static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer"; static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading"; static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES }; int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES };
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD }; float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE }; float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
@ -560,6 +562,8 @@ void AudioMixer::clearDomainSettings() {
void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
commonParseSettingsObject(settingsObject);
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) { if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject(); QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
const QString AUTO_THREADS = "auto_threads"; const QString AUTO_THREADS = "auto_threads";

View file

@ -988,6 +988,8 @@ void AvatarMixer::handlePacketVersionMismatch(PacketType type, const SockAddr& s
} }
void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
commonParseSettingsObject(domainSettings);
const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
QJsonObject avatarMixerGroupObject = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject(); QJsonObject avatarMixerGroupObject = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject();

View file

@ -272,7 +272,7 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN
#ifdef EXTRA_ERASE_DEBUGGING #ifdef EXTRA_ERASE_DEBUGGING
if (packetsSent > 0) { if (packetsSent > 0) {
qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of " qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of "
<< totalBytes << " total bytes to node:" << node->getUUID(); << totalBytes << " total bytes to node:" << node->getUUID();
} }
#endif #endif
@ -326,14 +326,14 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
} else { } else {
tree->setEntityScriptSourceWhitelist(""); tree->setEntityScriptSourceWhitelist("");
} }
auto entityEditFilters = DependencyManager::get<EntityEditFilters>(); auto entityEditFilters = DependencyManager::get<EntityEditFilters>();
QString filterURL; QString filterURL;
if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
// connect the filterAdded signal, and block edits until you hear back // connect the filterAdded signal, and block edits until you hear back
connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
entityEditFilters->addFilter(EntityItemID(), filterURL); entityEditFilters->addFilter(EntityItemID(), filterURL);
} }
} }
@ -367,7 +367,7 @@ void EntityServer::nodeKilled(SharedNodePointer node) {
// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad // FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
// set of stats to have, but we'd probably want a different data structure if we keep it very long. // set of stats to have, but we'd probably want a different data structure if we keep it very long.
// Since this version uses a single shared QMap for all senders, there could be some lock contention // Since this version uses a single shared QMap for all senders, there could be some lock contention
// on this QWriteLocker // on this QWriteLocker
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) { void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) {
QWriteLocker locker(&_viewerSendingStatsLock); QWriteLocker locker(&_viewerSendingStatsLock);

View file

@ -19,13 +19,8 @@
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME); setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME);
auto &ch = CrashHandler::getInstance();
ch.start(argv[0]);
ch.setAnnotation("program", "assignment-client");
AssignmentClientApp app(argc, argv); AssignmentClientApp app(argc, argv);
auto &ch = CrashHandler::getInstance();
ch.startMonitor(&app); ch.startMonitor(&app);

View file

@ -121,6 +121,8 @@ void MessagesMixer::domainSettingsRequestComplete() {
} }
void MessagesMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { void MessagesMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
commonParseSettingsObject(domainSettings);
const QString MESSAGES_MIXER_SETTINGS_KEY = "messages_mixer"; const QString MESSAGES_MIXER_SETTINGS_KEY = "messages_mixer";
QJsonObject messagesMixerGroupObject = domainSettings[MESSAGES_MIXER_SETTINGS_KEY].toObject(); QJsonObject messagesMixerGroupObject = domainSettings[MESSAGES_MIXER_SETTINGS_KEY].toObject();

View file

@ -1023,6 +1023,8 @@ void OctreeServer::readConfiguration() {
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject(); const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
commonParseSettingsObject(settingsObject);
QString settingsKey = getMyDomainSettingsKey(); QString settingsKey = getMyDomainSettingsKey();
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
_settings = settingsSectionObject; // keep this for later _settings = settingsSectionObject; // keep this for later

View file

@ -149,11 +149,14 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<Receiv
void EntityScriptServer::handleSettings() { void EntityScriptServer::handleSettings() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto& domainHandler = nodeList->getDomainHandler(); auto& domainHandler = nodeList->getDomainHandler();
const QJsonObject& settingsObject = domainHandler.getSettingsObject(); const QJsonObject& settingsObject = domainHandler.getSettingsObject();
commonParseSettingsObject(settingsObject);
static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server"; static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server";
if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) { if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) {
@ -292,7 +295,7 @@ void EntityScriptServer::run() {
entityScriptingInterface->init(); entityScriptingInterface->init();
_entityViewer.init(); _entityViewer.init();
// setup the JSON filter that asks for entities with a non-default serverScripts property // setup the JSON filter that asks for entities with a non-default serverScripts property
QJsonObject queryJSONParameters; QJsonObject queryJSONParameters;
queryJSONParameters[EntityJSONQueryProperties::SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault; queryJSONParameters[EntityJSONQueryProperties::SERVER_SCRIPTS_PROPERTY] = EntityQueryFilterSymbol::NonDefault;
@ -303,7 +306,7 @@ void EntityScriptServer::run() {
queryFlags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY] = true; queryFlags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY] = true;
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags; queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter // setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters); _entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
@ -380,7 +383,7 @@ void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
if (!hasAnotherEntityServer) { if (!hasAnotherEntityServer) {
clear(); clear();
} }
break; break;
} }
case NodeType::Agent: { case NodeType::Agent: {
@ -584,7 +587,7 @@ void EntityScriptServer::sendStatsPacket() {
} }
scriptEngineStats["number_running_scripts"] = numberRunningScripts; scriptEngineStats["number_running_scripts"] = numberRunningScripts;
statsObject["script_engine_stats"] = scriptEngineStats; statsObject["script_engine_stats"] = scriptEngineStats;
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
QJsonObject nodesObject; QJsonObject nodesObject;

View file

@ -76,7 +76,8 @@
{ {
"label": "Networking / Crash Reporting", "label": "Networking / Crash Reporting",
"name": "crash_reporting", "name": "crash_reporting",
"restart": false, "restart": true,
"assignment-types": [ 0, 1, 3, 4, 5, 6 ],
"settings": [ "settings": [
{ {
"name": "enable_crash_reporter", "name": "enable_crash_reporter",
@ -85,6 +86,22 @@
"default": false, "default": false,
"type": "checkbox", "type": "checkbox",
"advanced": true "advanced": true
},
{
"name": "custom_crash_url",
"label": "Custom crash URL",
"help": "If this is set, it overrides the internal crash reporting URL. This can be used for instance to direct crash reports to Sentry.",
"default": "",
"type": "string",
"advanced": true
},
{
"name": "custom_crash_token",
"label": "Custom crash token",
"help": "This is a token that identifies the thing sending a crash report, such as a product name and version number. If not set, the compile time default will be used.",
"default": "",
"type": "string",
"advanced": true
} }
] ]
}, },

View file

@ -63,7 +63,9 @@ const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
DomainServerSettingsManager::DomainServerSettingsManager() { DomainServerSettingsManager::DomainServerSettingsManager() {
// load the description object from the settings description // load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); qDebug() << "Application dir: " << QCoreApplication::applicationDirPath();
QString descriptionFilePath = QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH;
QFile descriptionFile(descriptionFilePath);
descriptionFile.open(QIODevice::ReadOnly); descriptionFile.open(QIODevice::ReadOnly);
QJsonParseError parseError; QJsonParseError parseError;
@ -89,7 +91,7 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
static const QString MISSING_SETTINGS_DESC_MSG = static const QString MISSING_SETTINGS_DESC_MSG =
QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3") QString("Did not find settings description in JSON at %1 - Unable to continue. domain-server will quit.\n%2 at %3")
.arg(SETTINGS_DESCRIPTION_RELATIVE_PATH).arg(parseError.errorString()).arg(parseError.offset); .arg(descriptionFilePath).arg(parseError.errorString()).arg(parseError.offset);
static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6; static const int MISSING_SETTINGS_DESC_ERROR_CODE = 6;
QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection, QMetaObject::invokeMethod(QCoreApplication::instance(), "queuedQuit", Qt::QueuedConnection,

View file

@ -50,12 +50,42 @@ enum SettingsType {
ContentSettings ContentSettings
}; };
/**
* @brief Manages the domain-wide settings
*
* The domain server doesn't use the interface's QSettings based system, but this one.
* The domain holds all the settings and distributes it to the connected assignment clients.
*
* Assignment clients request their settings by making a DomainSettingsRequest. The request
* is specific to the client's type.
*
* The response is generated in settingsResponseObjectForType and filtered according to what
* the client is allowed to see.
*
* To add a new setting, add it in resources/describe-settings.json
*
* To make a setting be sent to assignment clients, set the assignment-types value to an array
* of the desired assignment clients. The type is defined numerically.
*
* The canonical list of assignment types is in the Assignment::Type enum.
*
*
*
*/
class DomainServerSettingsManager : public QObject { class DomainServerSettingsManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
DomainServerSettingsManager(); DomainServerSettingsManager();
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
/**
* @brief Loads the configuration from the specified file
*
* Performs version upgrades when we're loading an older version of the config.
*
* @param userConfigFilename
*/
void setupConfigMap(const QString& userConfigFilename); void setupConfigMap(const QString& userConfigFilename);
// each of the three methods in this group takes a read lock of _settingsLock // each of the three methods in this group takes a read lock of _settingsLock
@ -126,7 +156,21 @@ public:
enum DefaultSettingsInclusion { NoDefaultSettings, IncludeDefaultSettings }; enum DefaultSettingsInclusion { NoDefaultSettings, IncludeDefaultSettings };
enum SettingsBackupFlag { NotForBackup, ForBackup }; enum SettingsBackupFlag { NotForBackup, ForBackup };
/// thread safe method to retrieve a JSON representation of settings /**
* @brief Generates a JSON representation of settings
*
* This is what answers an assignment client's request for domain settings.
*
* @note thread safe
*
* @param typeValue Type of assignment client
* @param authentication
* @param domainSettingsInclusion
* @param contentSettingsInclusion
* @param defaultSettingsInclusion
* @param settingsBackupFlag
* @return QJsonObject
*/
QJsonObject settingsResponseObjectForType(const QString& typeValue, QJsonObject settingsResponseObjectForType(const QString& typeValue,
SettingsRequestAuthentication authentication = NotAuthenticated, SettingsRequestAuthentication authentication = NotAuthenticated,
DomainSettingsInclusion domainSettingsInclusion = IncludeDomainSettings, DomainSettingsInclusion domainSettingsInclusion = IncludeDomainSettings,
@ -211,7 +255,7 @@ private:
// keep track of answers to api queries about which users are in which groups // keep track of answers to api queries about which users are in which groups
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>> QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
/// guard read/write access from multiple threads to settings /// guard read/write access from multiple threads to settings
QReadWriteLock _settingsLock { QReadWriteLock::Recursive }; QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
friend class DomainServer; friend class DomainServer;

View file

@ -35,7 +35,7 @@ int main(int argc, char* argv[]) {
// use a do-while to handle domain-server restart // use a do-while to handle domain-server restart
auto &ch = CrashHandler::getInstance(); auto &ch = CrashHandler::getInstance();
ch.start(argv[0]); ch.setPath(argv[0]);
if ( DomainServer::forceCrashReporting() ) { if ( DomainServer::forceCrashReporting() ) {
ch.setEnabled(true); ch.setEnabled(true);

View file

@ -323,6 +323,13 @@ int main(int argc, const char* argv[]) {
auto& ual = UserActivityLogger::getInstance(); auto& ual = UserActivityLogger::getInstance();
auto& ch = CrashHandler::getInstance(); auto& ch = CrashHandler::getInstance();
QObject::connect(&ch, &CrashHandler::enabledChanged, [](bool enabled) {
Settings s;
s.beginGroup("Crash");
s.setValue("ReportingEnabled", enabled);
s.endGroup();
});
// once the settings have been loaded, check if we need to flip the default for UserActivityLogger // once the settings have been loaded, check if we need to flip the default for UserActivityLogger
if (!ual.isDisabledSettingSet()) { if (!ual.isDisabledSettingSet()) {
// the user activity logger is opt-out for Interface // the user activity logger is opt-out for Interface
@ -337,13 +344,6 @@ int main(int argc, const char* argv[]) {
ch.setEnabled(true); ch.setEnabled(true);
} }
auto crashHandlerStarted = ch.start(argv[0]);
if (crashHandlerStarted) {
qDebug() << "Crash handler started";
} else {
qWarning() << "Crash handler failed to start";
}
ch.setAnnotation("program", "interface"); ch.setAnnotation("program", "interface");
const QString& applicationName = getInterfaceSharedMemoryName(); const QString& applicationName = getInterfaceSharedMemoryName();

View file

@ -1,6 +1,6 @@
set(TARGET_NAME monitoring) set(TARGET_NAME monitoring)
setup_hifi_library() setup_hifi_library()
link_hifi_libraries(shared networking) link_hifi_libraries(shared)
add_crashpad() add_crashpad()
target_breakpad() target_breakpad()

View file

@ -11,6 +11,8 @@
#include "CrashHandler.h" #include "CrashHandler.h"
#include "CrashHandlerBackend.h" #include "CrashHandlerBackend.h"
#include <QFileInfo>
#include <QCoreApplication>
CrashHandler& CrashHandler::getInstance() { CrashHandler& CrashHandler::getInstance() {
@ -18,13 +20,32 @@ CrashHandler& CrashHandler::getInstance() {
return sharedInstance; return sharedInstance;
} }
bool CrashHandler::start(const QString &path) { CrashHandler::CrashHandler(QObject *parent) : QObject(parent) {
}
void CrashHandler::setPath(const QString &path) {
QFileInfo fi(path);
if (isStarted()) { if (isStarted()) {
qCWarning(crash_handler) << "Crash handler already started"; qCWarning(crash_handler) << "Crash handler already started, too late to set the path.";
}
if (fi.isFile()) {
_path = fi.absolutePath();
} else {
_path = path;
}
}
bool CrashHandler::start() {
if (isStarted()) {
//qCWarning(crash_handler) << "Crash handler already started";
return false; return false;
} }
auto started = startCrashHandler(path.toStdString()); auto started = startCrashHandler(_path.toStdString(), _crashUrl.toStdString(), _crashToken.toStdString());
setStarted(started); setStarted(started);
if ( started ) { if ( started ) {
@ -41,21 +62,50 @@ void CrashHandler::startMonitor(QCoreApplication *app) {
} }
void CrashHandler::setEnabled(bool enabled) { void CrashHandler::setEnabled(bool enabled) {
if (enabled != _crashReportingEnabled.get()) { start();
_crashReportingEnabled.set(enabled);
if (enabled != _crashReportingEnabled) {
_crashReportingEnabled = enabled;
setCrashReportingEnabled(enabled); setCrashReportingEnabled(enabled);
emit enabledChanged(enabled);
}
}
void CrashHandler::setUrl(const QString &url) {
// This can be called both from the settings system in an assignment client
// and from the commandline parser. We only emit a warning if the commandline
// argument causes the domain setting to be ignored.
if (isStarted() && url != _crashUrl) {
qCWarning(crash_handler) << "Setting crash reporting URL to " << url << "after the crash handler is already running has no effect";
} else {
_crashUrl = url;
}
}
void CrashHandler::setToken(const QString &token) {
if (isStarted() && token != _crashToken) {
qCWarning(crash_handler) << "Setting crash reporting token to " << token << "after the crash handler is already running has no effect";
} else {
_crashToken = token;
} }
} }
void CrashHandler::setAnnotation(const std::string &key, const char *value) { void CrashHandler::setAnnotation(const std::string &key, const char *value) {
setAnnotation(key, std::string(value));
setCrashAnnotation(key, std::string(value)); setCrashAnnotation(key, std::string(value));
} }
void CrashHandler::setAnnotation(const std::string &key, const QString &value) { void CrashHandler::setAnnotation(const std::string &key, const QString &value) {
setCrashAnnotation(key, value.toStdString()); setAnnotation(key, value.toStdString());
} }
void CrashHandler::setAnnotation(const std::string &key, const std::string &value) { void CrashHandler::setAnnotation(const std::string &key, const std::string &value) {
if (!isStarted()) {
qCWarning(crash_handler) << "Can't set annotation" << QString::fromStdString(key) << "to" << QString::fromStdString(value) << "crash handler not yet started";
return;
}
setCrashAnnotation(key, value); setCrashAnnotation(key, value);
} }

View file

@ -21,7 +21,31 @@
/** /**
* @brief The global object in charge of setting up and controlling crash reporting. * @brief The global object in charge of setting up and controlling crash reporting.
* *
* This object initializes and talks to crash reporting backends. * This object initializes and talks to crash reporting backends. For those, see
* CrashHandlerBackend.h and the .cpp files that implement that interface.
*
* The crash URL and token can only be passed to the underlying system on start, so
* things should be set up in such a way that startup is only done after those are set.
*
* start() will be automatically called when setEnabled() is called with true.
* setAnnotation() can only be called after start.
*
*
* To use, follow this general pattern in an application:
*
* @code {.cpp}
* auto &ch = CrashHandler::getInstance();
* ch.setPath(...);
* ch.setUrl("https://server.com/crash-reports");
* ch.setToken("1.2beta");
* ch.setEnabled(true);
* ch.setAnnotation("version", "1.3"); // Needs a started handler to work
* @endcode
*
* For an assignment client, there are two potential ways to start, through the command-line
* and through the settings system. Since the path, URL and token only apply on startup, the
* code must be written such that if command arguments are not given, setEnabled() or start()
* are not called until receiving the settings from the domain.
* *
*/ */
class CrashHandler : public QObject { class CrashHandler : public QObject {
@ -30,18 +54,42 @@ class CrashHandler : public QObject {
public: public:
static CrashHandler& getInstance(); static CrashHandler& getInstance();
public slots: public slots:
/**
* @brief Set the directory for the crash reports
*
* This sets the path for writing crash reports. This should be done on application startup.
*
* @param path Directory where to store crash reports. It's allowed to set this to argv[0],
* if the path is a filename, then the base directory will be automatically used.
*/
void setPath(const QString &path);
/** /**
* @brief Start the crash handler * @brief Start the crash handler
* *
* @param path Database path * This is called automatically if it wasn't started yet when setEnabled() is called.
*
* @param path Path where to store the crash database
* @return true Started successfully * @return true Started successfully
* @return false Failed to start * @return false Failed to start
*/ */
bool start(const QString &path); bool start();
/**
* @brief Starts the unhandled exception monitor.
*
* On Windows, it's possible for the unhandled exception handler to be reset. This starts a timer
* to periodically set it back.
*
* On non-Windows systems this has no effect.
*
* @param app Main application
*/
void startMonitor(QCoreApplication *app); void startMonitor(QCoreApplication *app);
@ -70,7 +118,7 @@ public slots:
* @return true Crashes will be reported to CMAKE_BACKTRACE_URL * @return true Crashes will be reported to CMAKE_BACKTRACE_URL
* @return false Crashes will not be reported * @return false Crashes will not be reported
*/ */
bool isEnabled() const { return _crashReportingEnabled.get(); } bool isEnabled() const { return _crashReportingEnabled; }
/** /**
* @brief Set whether we want to submit crash reports to the report server * @brief Set whether we want to submit crash reports to the report server
@ -78,17 +126,84 @@ public slots:
* The report server is configured with CMAKE_BACKTRACE_URL. * The report server is configured with CMAKE_BACKTRACE_URL.
* Emits crashReportingEnabledChanged signal. * Emits crashReportingEnabledChanged signal.
* *
* @note This automatically calls start(), so it should be called after setPath(), setUrl() and setToken()
* @param enabled Whether it's enabled. * @param enabled Whether it's enabled.
*/ */
void setEnabled(bool enabled); void setEnabled(bool enabled);
/**
* @brief Set the URL where to send crash reports to
*
* If not set, a predefined URL specified at compile time via CMAKE_BACKTRACE_URL
* will be used.
*
* @param url URL
*/
void setUrl(const QString &url);
/**
* @brief Set the token for the crash reporter
*
* This is an identifier in the crash collection service, such as Sentry, and may contain
* a branch name or a version number.
*
* If not set, a predefined token specified at compile time via CMAKE_BACKTRACE_TOKEN
* will be used.
*
* @param token Token
*/
void setToken(const QString &token);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const char *value); void setAnnotation(const std::string &key, const char *value);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const QString &value); void setAnnotation(const std::string &key, const QString &value);
/**
* @brief Set an annotation to be added to a crash
*
* Annotations add extra information, such as the application's version number,
* the current user, or any other information of interest.
*
* @param key Key
* @param value Value
*/
void setAnnotation(const std::string &key, const std::string &value); void setAnnotation(const std::string &key, const std::string &value);
signals:
/**
* @brief Emitted when the enabled/disabled state of the crash handler changes
*
* This can be used to store it as a setting.
*
* @param enabled Whether the crash handler is now enabled
*/
void enabledChanged(bool enabled);
private: private:
CrashHandler(QObject *parent = nullptr);
/** /**
* @brief Marks the crash monitor as started * @brief Marks the crash monitor as started
* *
@ -99,9 +214,12 @@ private:
void setStarted(bool started) { _crashMonitorStarted = started; } void setStarted(bool started) { _crashMonitorStarted = started; }
Setting::Handle<bool> _crashReportingEnabled { "CrashReportingEnabled", false };
bool _crashMonitorStarted {false}; bool _crashMonitorStarted {false};
bool _crashReportingEnabled {false};
QString _path;
QString _crashUrl;
QString _crashToken;
}; };

View file

@ -18,7 +18,7 @@
Q_DECLARE_LOGGING_CATEGORY(crash_handler) Q_DECLARE_LOGGING_CATEGORY(crash_handler)
bool startCrashHandler(std::string appPath); bool startCrashHandler(std::string appPath, std::string url="", std::string token="");
void setCrashAnnotation(std::string name, std::string value); void setCrashAnnotation(std::string name, std::string value);
void startCrashHookMonitor(QCoreApplication* app); void startCrashHookMonitor(QCoreApplication* app);
void setCrashReportingEnabled(bool value); void setCrashReportingEnabled(bool value);

View file

@ -57,7 +57,7 @@ void flushAnnotations() {
settings.sync(); settings.sync();
} }
bool startCrashHandler(std::string appPath) { bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) {
annotations["version"] = BuildInfo::VERSION; annotations["version"] = BuildInfo::VERSION;
annotations["build_number"] = BuildInfo::BUILD_NUMBER; annotations["build_number"] = BuildInfo::BUILD_NUMBER;
annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING; annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING;

View file

@ -49,6 +49,10 @@ Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler")
static const std::string BACKTRACE_URL{ CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_URL{ CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN{ CMAKE_BACKTRACE_TOKEN }; static const std::string BACKTRACE_TOKEN{ CMAKE_BACKTRACE_TOKEN };
std::string custom_backtrace_url;
std::string custom_backtrace_token;
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire // SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire
// note that this code will malfunction if you attempt to grab a lock while already holding it // note that this code will malfunction if you attempt to grab a lock while already holding it
@ -351,8 +355,16 @@ static QString findBinaryDir() {
return QString(); return QString();
} }
bool startCrashHandler(std::string appPath) { bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) {
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { if (crashURL.empty()) {
crashURL = BACKTRACE_URL;
}
if (crashToken.empty()) {
crashToken = BACKTRACE_TOKEN;
}
if (crashURL.empty() || crashToken.empty()) {
qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled."; qCCritical(crash_handler) << "Backtrace URL or token not set, crash handler disabled.";
return false; return false;
} }
@ -362,7 +374,7 @@ bool startCrashHandler(std::string appPath) {
std::vector<std::string> arguments; std::vector<std::string> arguments;
std::map<std::string, std::string> annotations; std::map<std::string, std::string> annotations;
annotations["sentry[release]"] = BACKTRACE_TOKEN; annotations["sentry[release]"] = crashToken;
annotations["sentry[contexts][app][app_version]"] = BuildInfo::VERSION.toStdString(); annotations["sentry[contexts][app][app_version]"] = BuildInfo::VERSION.toStdString();
annotations["sentry[contexts][app][app_build]"] = BuildInfo::BUILD_NUMBER.toStdString(); annotations["sentry[contexts][app][app_build]"] = BuildInfo::BUILD_NUMBER.toStdString();
annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString(); annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString();
@ -389,10 +401,10 @@ bool startCrashHandler(std::string appPath) {
qCDebug(crash_handler) << "Locating own directory by platform-specific method"; qCDebug(crash_handler) << "Locating own directory by platform-specific method";
interfaceDir.setPath(binaryDir); interfaceDir.setPath(binaryDir);
} else { } else {
// Getting the base dir from argv[0] is already handled by CrashHandler, so we use
// the path as-is here.
qCDebug(crash_handler) << "Locating own directory by argv[0]"; qCDebug(crash_handler) << "Locating own directory by argv[0]";
interfaceDir.setPath(QString::fromStdString(appPath)); interfaceDir.setPath(QString::fromStdString(appPath));
// argv[0] gets us the path including the binary file
interfaceDir.cdUp();
} }
if (!interfaceDir.exists(CRASHPAD_HANDLER_NAME)) { if (!interfaceDir.exists(CRASHPAD_HANDLER_NAME)) {
@ -429,7 +441,7 @@ bool startCrashHandler(std::string appPath) {
crashpadDatabase->GetSettings()->SetUploadsEnabled(CrashHandler::getInstance().isEnabled()); crashpadDatabase->GetSettings()->SetUploadsEnabled(CrashHandler::getInstance().isEnabled());
if (!client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true)) { if (!client->StartHandler(handler, db, db, crashURL, annotations, arguments, true, true)) {
qCCritical(crash_handler) << "Failed to start crashpad handler"; qCCritical(crash_handler) << "Failed to start crashpad handler";
return false; return false;
} }

View file

@ -20,7 +20,7 @@
Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler") Q_LOGGING_CATEGORY(crash_handler, "overte.crash_handler")
bool startCrashHandler(std::string appPath) { bool startCrashHandler(std::string appPath, std::string crashURL, std::string crashToken) {
qCWarning(crash_handler) << "No crash handler available."; qCWarning(crash_handler) << "No crash handler available.";
return false; return false;
} }

View file

@ -1,6 +1,6 @@
set(TARGET_NAME networking) set(TARGET_NAME networking)
setup_hifi_library(Network WebSockets) setup_hifi_library(Network WebSockets)
link_hifi_libraries(shared platform) link_hifi_libraries(shared platform monitoring)
target_openssl() target_openssl()
target_tbb() target_tbb()

View file

@ -16,11 +16,17 @@
#include <BuildInfo.h> #include <BuildInfo.h>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QJsonObject>
#include <crash-handler/CrashHandler.h>
#include "udt/PacketHeaders.h" #include "udt/PacketHeaders.h"
#include "SharedUtil.h" #include "SharedUtil.h"
#include "UUID.h" #include "UUID.h"
static const QString CRASH_REPORTING_GROUP_KEY = "crash_reporting";
Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
switch (nodeType) { switch (nodeType) {
case NodeType::AudioMixer: case NodeType::AudioMixer:
@ -51,7 +57,7 @@ Assignment::Assignment() :
_payload(), _payload(),
_isStatic(false) _isStatic(false)
{ {
} }
Assignment::Assignment(Assignment::Command command, Assignment::Type type, const QString& pool, Assignment::Location location, QString dataDirectory) : Assignment::Assignment(Assignment::Command command, Assignment::Type type, const QString& pool, Assignment::Location location, QString dataDirectory) :
@ -84,9 +90,9 @@ Assignment::Assignment(ReceivedMessage& message) :
} else if (message.getType() == PacketType::CreateAssignment) { } else if (message.getType() == PacketType::CreateAssignment) {
_command = Assignment::CreateCommand; _command = Assignment::CreateCommand;
} }
QDataStream packetStream(message.getMessage()); QDataStream packetStream(message.getMessage());
packetStream >> *this; packetStream >> *this;
} }
@ -113,7 +119,7 @@ Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
void Assignment::swap(Assignment& otherAssignment) { void Assignment::swap(Assignment& otherAssignment) {
using std::swap; using std::swap;
swap(_uuid, otherAssignment._uuid); swap(_uuid, otherAssignment._uuid);
swap(_command, otherAssignment._command); swap(_command, otherAssignment._command);
swap(_type, otherAssignment._type); swap(_type, otherAssignment._type);
@ -148,10 +154,36 @@ const char* Assignment::typeToString(Assignment::Type type) {
} }
} }
void Assignment::commonParseSettingsObject(const QJsonObject &settingsObject) {
if (settingsObject.contains(CRASH_REPORTING_GROUP_KEY)) {
auto &ch = CrashHandler::getInstance();
QJsonObject crashGroupObject = settingsObject[CRASH_REPORTING_GROUP_KEY].toObject();
const QString CRASH_REPORTING_ENABLED = "enable_crash_reporter";
const QString CRASH_REPORTING_CUSTOM_URL = "custom_crash_url";
const QString CRASH_REPORTING_CUSTOM_TOKEN = "custom_crash_token";
bool enabled = crashGroupObject[CRASH_REPORTING_ENABLED].toBool();
QString url = crashGroupObject[CRASH_REPORTING_CUSTOM_URL].toString();
QString token = crashGroupObject[CRASH_REPORTING_CUSTOM_TOKEN].toString();
ch.setUrl(url);
ch.setToken(token);
ch.setEnabled(enabled);
ch.setAnnotation("program", "assignment-client");
ch.setAnnotation("assignment-client", "audio-mixer");
}
}
QDebug operator<<(QDebug debug, const Assignment &assignment) { QDebug operator<<(QDebug debug, const Assignment &assignment) {
debug.nospace() << "UUID: " << qPrintable(assignment.getUUID().toString()) << debug.nospace() << "UUID: " << qPrintable(assignment.getUUID().toString()) <<
", Type: " << assignment.getTypeName() << " (" << assignment.getType() << ")"; ", Type: " << assignment.getTypeName() << " (" << assignment.getType() << ")";
if (!assignment.getPool().isEmpty()) { if (!assignment.getPool().isEmpty()) {
debug << ", Pool: " << assignment.getPool(); debug << ", Pool: " << assignment.getPool();
} }
@ -161,11 +193,11 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) {
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload; out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
if (assignment._command == Assignment::RequestCommand) { if (assignment._command == Assignment::RequestCommand) {
out << assignment._nodeVersion; out << assignment._nodeVersion;
} }
return out; return out;
} }
@ -173,11 +205,11 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) {
quint8 packedType; quint8 packedType;
in >> packedType >> assignment._uuid >> assignment._pool >> assignment._payload; in >> packedType >> assignment._uuid >> assignment._pool >> assignment._payload;
assignment._type = (Assignment::Type) packedType; assignment._type = (Assignment::Type) packedType;
if (assignment._command == Assignment::RequestCommand) { if (assignment._command == Assignment::RequestCommand) {
in >> assignment._nodeVersion; in >> assignment._nodeVersion;
} }
return in; return in;
} }
@ -187,3 +219,4 @@ uint qHash(const Assignment::Type& key, uint seed) {
// strongly typed enum for PacketType // strongly typed enum for PacketType
return qHash((uint8_t) key, seed); return qHash((uint8_t) key, seed);
} }

View file

@ -14,10 +14,12 @@
#include <QtCore/QUuid> #include <QtCore/QUuid>
#include "ReceivedMessage.h" #include "ReceivedMessage.h"
#include "NodeList.h" #include "NodeList.h"
const int MAX_PAYLOAD_BYTES = 1024; const int MAX_PAYLOAD_BYTES = 1024;
const QString emptyPool = QString(); const QString emptyPool = QString();
@ -80,12 +82,12 @@ public:
void setPool(const QString& pool) { _pool = pool; }; void setPool(const QString& pool) { _pool = pool; };
const QString& getPool() const { return _pool; } const QString& getPool() const { return _pool; }
void setIsStatic(bool isStatic) { _isStatic = isStatic; } void setIsStatic(bool isStatic) { _isStatic = isStatic; }
bool isStatic() const { return _isStatic; } bool isStatic() const { return _isStatic; }
const QString& getNodeVersion() const { return _nodeVersion; } const QString& getNodeVersion() const { return _nodeVersion; }
const char* getTypeName() const; const char* getTypeName() const;
static const char* typeToString(Assignment::Type type); static const char* typeToString(Assignment::Type type);
@ -94,6 +96,16 @@ public:
friend QDataStream& operator>>(QDataStream &in, Assignment& assignment); friend QDataStream& operator>>(QDataStream &in, Assignment& assignment);
protected: protected:
/**
* @brief Parse the part of the settings object common to all assignment clients
*
* Currently this is the crash reporting settings.
*
* @param settingsObject
*/
void commonParseSettingsObject(const QJsonObject &settingsObject);
QUuid _uuid; /// the 16 byte UUID for this assignment QUuid _uuid; /// the 16 byte UUID for this assignment
Assignment::Command _command; /// the command for this assignment (Create, Deploy, Request) Assignment::Command _command; /// the command for this assignment (Create, Deploy, Request)
Assignment::Type _type; /// the type of the assignment, defines what the assignee will do Assignment::Type _type; /// the type of the assignment, defines what the assignee will do