diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f553b4bbdf..d20a62fc7b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,8 @@ #include "InterfaceParentFinder.h" #include "FrameTimingsScriptingInterface.h" +#include +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -435,7 +438,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); @@ -673,10 +676,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : accountManager->setIsAgent(true); accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get()); - auto addressManager = DependencyManager::get(); // use our MyAvatar position and quat for address manager path @@ -766,6 +765,39 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Make sure we don't time out during slow operations at startup updateHeartbeat(); + + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["slVersion"] }, + { "gl_renderer", glContextData["renderer"] } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + UserActivityLogger::getInstance().logAction("launch", properties); + + // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); _entityEditSender.setMyAvatar(getMyAvatar()); @@ -1061,6 +1093,89 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } }); + // Add periodic checks to send user activity data + static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; + static int SEND_STATS_INTERVAL_MS = 10000; + static int NEARBY_AVATAR_RADIUS_METERS = 10; + + // Periodically send fps as a user activity event + QTimer* sendStatsTimer = new QTimer(this); + sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); + connect(sendStatsTimer, &QTimer::timeout, this, [this]() { + QJsonObject properties = {}; + MemoryInfo memInfo; + if (getMemoryInfo(memInfo)) { + properties["system_memory_total"] = static_cast(memInfo.totalMemoryBytes); + properties["system_memory_used"] = static_cast(memInfo.usedMemoryBytes); + properties["process_memory_used"] = static_cast(memInfo.processUsedMemoryBytes); + } + + auto displayPlugin = qApp->getActiveDisplayPlugin(); + + properties["fps"] = _frameCounter.rate(); + properties["present_rate"] = displayPlugin->presentRate(); + properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); + properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); + properties["sim_rate"] = getAverageSimsPerSecond(); + properties["avatar_sim_rate"] = getAvatarSimrate(); + + auto bandwidthRecorder = DependencyManager::get(); + properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); + properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); + properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); + properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); + + auto nodeList = DependencyManager::get(); + SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer); + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer); + SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer); + properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1; + properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1; + properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1; + properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1; + properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; + + auto loadingRequests = ResourceCache::getLoadingRequests(); + properties["active_downloads"] = loadingRequests.size(); + properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); + + properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; + + UserActivityLogger::getInstance().logAction("stats", properties); + }); + sendStatsTimer->start(); + + + // Periodically check for count of nearby avatars + static int lastCountOfNearbyAvatars = -1; + QTimer* checkNearbyAvatarsTimer = new QTimer(this); + checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); + connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { + auto avatarManager = DependencyManager::get(); + int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(), + NEARBY_AVATAR_RADIUS_METERS) - 1; + if (nearbyAvatars != lastCountOfNearbyAvatars) { + lastCountOfNearbyAvatars = nearbyAvatars; + UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } }); + } + }); + checkNearbyAvatarsTimer->start(); + + // Track user activity event when we receive a mute packet + auto onMutedByMixer = []() { + UserActivityLogger::getInstance().logAction("received_mute_packet"); + }; + connect(DependencyManager::get().data(), &AudioClient::mutedByMixer, this, onMutedByMixer); + + // Track when the address bar is opened + auto onAddressBarToggled = [this]() { + // Record time + UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } }); + }; + connect(DependencyManager::get().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled); + // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -4574,6 +4689,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); + + scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); } bool Application::canAcceptURL(const QString& urlString) const { @@ -5176,6 +5293,11 @@ void Application::updateDisplayMode() { return; } + UserActivityLogger::getInstance().logAction("changed_display_mode", { + { "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" }, + { "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" } + }); + auto offscreenUi = DependencyManager::get(); // Make the switch atomic from the perspective of other threads diff --git a/interface/src/Application.h b/interface/src/Application.h index 114ce27144..5beaa5b455 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -211,6 +211,8 @@ public: float getRenderResolutionScale() const; + qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } + bool isAboutToQuit() const { return _aboutToQuit; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 24256fdf39..c4d985419e 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -80,7 +80,8 @@ void DiscoverabilityManager::updateLocation() { locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends)); // if we have a session ID add it now, otherwise add a null value - rootObject[SESSION_ID_KEY] = _sessionID.isEmpty() ? QJsonValue() : _sessionID; + auto sessionID = accountManager->getSessionID(); + rootObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString(); JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = this; @@ -110,11 +111,8 @@ void DiscoverabilityManager::updateLocation() { callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; QJsonObject heartbeatObject; - if (!_sessionID.isEmpty()) { - heartbeatObject[SESSION_ID_KEY] = _sessionID; - } else { - heartbeatObject[SESSION_ID_KEY] = QJsonValue(); - } + auto sessionID = accountManager->getSessionID(); + heartbeatObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString(); accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, @@ -126,11 +124,11 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply auto dataObject = AccountManager::dataObjectFromResponse(requestReply); if (!dataObject.isEmpty()) { - _sessionID = dataObject[SESSION_ID_KEY].toString(); + auto sessionID = dataObject[SESSION_ID_KEY].toString(); // give that session ID to the account manager auto accountManager = DependencyManager::get(); - accountManager->setSessionID(_sessionID); + accountManager->setSessionID(sessionID); } } diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 9a1fa7b39c..196b0cdf81 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -49,7 +49,6 @@ private: DiscoverabilityManager(); Setting::Handle _mode; - QString _sessionID; QJsonObject _lastLocationObject; }; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 9084fd837b..d153cfd977 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -44,6 +44,20 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range return false; } +int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { + auto hashCopy = getHashCopy(); + auto rangeMeters2 = rangeMeters * rangeMeters; + int count = 0; + for (const AvatarSharedPointer& sharedAvatar : hashCopy) { + glm::vec3 avatarPosition = sharedAvatar->getPosition(); + auto distance2 = glm::distance2(avatarPosition, position); + if (distance2 < rangeMeters2) { + ++count; + } + } + return count; +} + AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 5f58074427..9d3ebb60f5 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -39,6 +39,7 @@ public: Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); } + int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); signals: void avatarAddedEvent(const QUuid& sessionUUID); diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 302e0b8515..79b39a2331 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -5,6 +5,7 @@ #include #include #include +#include const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; @@ -39,6 +40,13 @@ const QGLFormat& getDefaultGLFormat() { return glFormat; } +int glVersionToInteger(QString glVersion) { + QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); + int majorNumber = versionParts[0].toInt(); + int minorNumber = versionParts[1].toInt(); + return majorNumber * 100 + minorNumber * 10; +} + QJsonObject getGLContextData() { if (!QOpenGLContext::currentContext()) { return QJsonObject(); diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index ddb254f1c5..477bf7abc8 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -27,5 +27,6 @@ void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVer const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); const QGLFormat& getDefaultGLFormat(); QJsonObject getGLContextData(); +int glVersionToInteger(QString glVersion); #endif diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 26b3801ec1..b6c5e691a6 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -44,6 +44,7 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; +static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, QObject* errorCallbackReceiver, const QString& errorCallbackMethod, @@ -222,8 +223,7 @@ void AccountManager::sendRequest(const QString& path, // if we're allowed to send usage data, include whatever the current session ID is with this request auto& activityLogger = UserActivityLogger::getInstance(); if (activityLogger.isEnabled()) { - static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID"; - networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); } @@ -322,6 +322,9 @@ void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); if (requestReply->error() == QNetworkReply::NoError) { + if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { + _sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER); + } passSuccessToCallback(requestReply); } else { passErrorToCallback(requestReply); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 4803d2625f..d30a05fb2c 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -26,9 +26,9 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(), - QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(), - QObject* updateReceiver = NULL, const QString& updateSlot = QString()); + JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(), + QObject* updateReceiver = nullptr, const QString& updateSlot = QString()); bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } @@ -86,6 +86,7 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + QUuid getSessionID() const { return _sessionID; } void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } public slots: @@ -139,7 +140,7 @@ private: bool _isWaitingForKeypairResponse { false }; QByteArray _pendingPrivateKey; - QUuid _sessionID; + QUuid _sessionID { QUuid::createUuid() }; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 80989acd2c..df9b4094b0 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -23,6 +23,7 @@ #include "AddressManager.h" #include "NodeList.h" #include "NetworkLogging.h" +#include "UserActivityLogger.h" const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; @@ -130,6 +131,10 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { } bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { + static QString URL_TYPE_USER = "user"; + static QString URL_TYPE_DOMAIN_ID = "domain_id"; + static QString URL_TYPE_PLACE = "place"; + static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; if (lookupUrl.scheme() == HIFI_URL_SCHEME) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); @@ -147,6 +152,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (handleUsername(lookupUrl.authority())) { // handled a username for lookup + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); + // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API _previousLookup = lookupUrl; @@ -157,6 +164,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); + // a network address lookup clears the previous lookup since we don't expect to re-attempt it _previousLookup.clear(); @@ -174,6 +183,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info _previousLookup = lookupUrl; @@ -181,6 +192,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { + UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); + // store this place name as the previous lookup in case we fail to connect and want to refresh API info _previousLookup = lookupUrl; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 83c6eb304e..eba4d31167 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -18,6 +18,7 @@ #include "UserActivityLogger.h" #include +#include "AddressManager.h" static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; @@ -125,6 +126,19 @@ void UserActivityLogger::changedDomain(QString domainURL) { } void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) { + static QStringList DEVICE_BLACKLIST = { + "Desktop", + "NullDisplayPlugin", + "3D TV - Side by Side Stereo", + "3D TV - Interleaved", + + "Keyboard/Mouse" + }; + + if (DEVICE_BLACKLIST.contains(deviceName)) { + return; + } + const QString ACTION_NAME = "connected_device"; QJsonObject actionDetails; const QString TYPE_OF_DEVICE = "type_of_device"; @@ -148,12 +162,34 @@ void UserActivityLogger::loadedScript(QString scriptName) { } -void UserActivityLogger::wentTo(QString destinationType, QString destinationName) { +void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QString destinationType, QString destinationName) { + // Only accept these types of triggers. Other triggers are usually used internally in AddressManager. + QString trigger; + switch (lookupTrigger) { + case AddressManager::UserInput: + trigger = "UserInput"; + break; + case AddressManager::Back: + trigger = "Back"; + break; + case AddressManager::Forward: + trigger = "Forward"; + break; + case AddressManager::StartupFromSettings: + trigger = "StartupFromSettings"; + break; + default: + return; + } + + const QString ACTION_NAME = "went_to"; QJsonObject actionDetails; + const QString TRIGGER_TYPE_KEY = "trigger"; const QString DESTINATION_TYPE_KEY = "destination_type"; const QString DESTINATION_NAME_KEY = "detination_name"; + actionDetails.insert(TRIGGER_TYPE_KEY, trigger); actionDetails.insert(DESTINATION_TYPE_KEY, destinationType); actionDetails.insert(DESTINATION_NAME_KEY, destinationName); diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index c2ab93db2f..b41960a8ad 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -20,6 +20,7 @@ #include #include +#include "AddressManager.h" class UserActivityLogger : public QObject { Q_OBJECT @@ -42,7 +43,7 @@ public slots: void changedDomain(QString domainURL); void connectedDevice(QString typeOfDevice, QString deviceName); void loadedScript(QString scriptName); - void wentTo(QString destinationType, QString destinationName); + void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName); private slots: void requestError(QNetworkReply& errorReply); diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp new file mode 100644 index 0000000000..8b22b8ff58 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "UserActivityLoggerScriptingInterface.h" +#include "UserActivityLogger.h" + +void UserActivityLoggerScriptingInterface::enabledEdit() { + logAction("enabled_edit"); +} + +void UserActivityLoggerScriptingInterface::openedMarketplace() { + logAction("opened_marketplace"); +} + +void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) { + logAction("toggled_away", { { "is_away", isAway } }); +} + +void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) { + QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction", + Q_ARG(QString, action), + Q_ARG(QJsonObject, details)); +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h new file mode 100644 index 0000000000..9d60d666e2 --- /dev/null +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -0,0 +1,31 @@ +// +// UserActivityLoggerScriptingInterface.h +// libraries/networking/src +// +// Created by Ryan Huffman on 6/06/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_UserActivityLoggerScriptingInterface_h +#define hifi_UserActivityLoggerScriptingInterface_h + +#include +#include + +#include + +class UserActivityLoggerScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE void enabledEdit(); + Q_INVOKABLE void openedMarketplace(); + Q_INVOKABLE void toggledAway(bool isAway); + +private: + void logAction(QString action, QJsonObject details = {}); +}; + +#endif // hifi_UserActivityLoggerScriptingInterface_h diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index fb5bf0ba55..0452c7fbfe 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -15,6 +15,7 @@ #include "Forward.h" class Plugin : public QObject { + Q_OBJECT public: /// \return human-readable name virtual const QString& getName() const = 0; @@ -63,6 +64,13 @@ public: virtual void saveSettings() const {} virtual void loadSettings() {} +signals: + // These signals should be emitted when a device is first known to be available. In some cases this will + // be in `init()`, in other cases, like Neuron, this isn't known until activation. + // SDL2 isn't a device itself, but can have 0+ subdevices. subdeviceConnected is used in this case. + void deviceConnected(QString pluginName) const; + void subdeviceConnected(QString pluginName, QString subdeviceName) const; + protected: bool _active { false }; PluginContainer* _container { nullptr }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index d5c860200a..29658eeb6b 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,6 +14,9 @@ #include #include +#include +#include + #include "RuntimePlugin.h" #include "DisplayPlugin.h" #include "InputPlugin.h" @@ -119,6 +122,15 @@ static DisplayPluginList displayPlugins; const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("display", pluginName + " | " + deviceName); + }; + std::call_once(once, [&] { // Grab the built in plugins displayPlugins = ::getDisplayPlugins(); @@ -133,6 +145,8 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { } } for (auto plugin : displayPlugins) { + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); plugin->setContainer(_container); plugin->init(); } @@ -154,6 +168,15 @@ void PluginManager::disableDisplayPlugin(const QString& name) { const InputPluginList& PluginManager::getInputPlugins() { static InputPluginList inputPlugins; static std::once_flag once; + static auto deviceAddedCallback = [](QString deviceName) { + qDebug() << "Added device: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", deviceName); + }; + static auto subdeviceAddedCallback = [](QString pluginName, QString deviceName) { + qDebug() << "Added subdevice: " << deviceName; + UserActivityLogger::getInstance().connectedDevice("input", pluginName + " | " + deviceName); + }; + std::call_once(once, [&] { inputPlugins = ::getInputPlugins(); @@ -170,6 +193,8 @@ const InputPluginList& PluginManager::getInputPlugins() { } for (auto plugin : inputPlugins) { + connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); + connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); plugin->setContainer(_container); plugin->init(); } diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 19838964a4..02f92d87e7 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -122,7 +122,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) } if (count > bestCount) { bestCount = count; - _name = sString; + _name = QString(sString).trimmed(); hr = spInstance->Get(CComBSTR(_T("DriverVersion")), 0, &var, 0, 0); if (hr == S_OK) { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index b80fac637c..edb6fe437d 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -28,6 +28,7 @@ #ifdef Q_OS_WIN #include "CPUIdent.h" +#include #endif @@ -843,3 +844,29 @@ void printSystemInformation() { (envVariables.contains(env) ? " = " + envVariables.value(env) : " NOT FOUND"); } } + +bool getMemoryInfo(MemoryInfo& info) { +#ifdef Q_OS_WIN + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(ms); + if (!GlobalMemoryStatusEx(&ms)) { + return false; + } + + info.totalMemoryBytes = ms.ullTotalPhys; + info.availMemoryBytes = ms.ullAvailPhys; + info.usedMemoryBytes = ms.ullTotalPhys - ms.ullAvailPhys; + + + PROCESS_MEMORY_COUNTERS_EX pmc; + if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&pmc), sizeof(pmc))) { + return false; + } + info.processUsedMemoryBytes = pmc.PrivateUsage; + info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage; + + return true; +#endif + + return false; +} \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 042396f474..f3e5625484 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -204,4 +204,14 @@ void disableQtBearerPoll(); void printSystemInformation(); +struct MemoryInfo { + uint64_t totalMemoryBytes; + uint64_t availMemoryBytes; + uint64_t usedMemoryBytes; + uint64_t processUsedMemoryBytes; + uint64_t processPeakUsedMemoryBytes; +}; + +bool getMemoryInfo(MemoryInfo& info); + #endif // hifi_SharedUtil_h diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index 0a4bc7f8d2..e41472a8c5 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -387,6 +387,8 @@ bool NeuronPlugin::activate() { } else { qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort; + emit deviceConnected(getName()); + BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); return true; } diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 09e783864c..b9a19658e2 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -66,6 +66,7 @@ void SDL2Manager::init() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } } @@ -157,6 +158,7 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati _openJoysticks[id] = joystick; userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { if (_openJoysticks.contains(event.cdevice.which)) { diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 566f879f69..03028249a3 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -137,6 +137,12 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED + static bool sixenseHasBeenConnected { false }; + if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) { + sixenseHasBeenConnected = true; + emit deviceConnected(getName()); + } + auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); diff --git a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp index 0c29ced6f9..4641799b79 100644 --- a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp @@ -58,7 +58,6 @@ bool SpacemouseManager::activate() { if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", NAME); } return true; } @@ -329,7 +328,6 @@ bool SpacemouseManager::RawInputEventFilter(void* msg, long* result) { auto userInputMapper = DependencyManager::get(); if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); } else if (!Is3dmouseAttached() && instance->getDeviceID() != controller::Input::INVALID_DEVICE) { userInputMapper->removeDevice(instance->getDeviceID()); @@ -856,7 +854,7 @@ void SpacemouseManager::init() { if (Is3dmouseAttached() && instance->getDeviceID() == controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); + emit deviceConnected(getName()); } //let one axis be dominant //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL); diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 1006d69f06..2b2ec5bdb0 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -28,6 +28,12 @@ bool OculusDisplayPlugin::internalActivate() { return result; } +void OculusDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusDisplayPlugin::cycleDebugOutput() { if (_session) { currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index d6cd6f6f3d..ed6e0d13ea 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -17,6 +17,8 @@ class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: const QString& getName() const override { return NAME; } + void init() override; + QString getPreferredAudioInDevice() const override; QString getPreferredAudioOutDevice() const override; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 4aadb890d5..f1a803ee19 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -36,6 +36,12 @@ const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } +void OculusLegacyDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + void OculusLegacyDisplayPlugin::resetSensors() { ovrHmd_RecenterPose(_hmd); } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 453a6f9168..6ffc1a7f44 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -23,6 +23,8 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + void init() override; + int getHmdScreen() const override; // Stereo specific methods diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index c5d3be25b2..88b0665dfb 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -41,6 +41,12 @@ bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } +void OpenVrDisplayPlugin::init() { + Plugin::init(); + + emit deviceConnected(getName()); +} + bool OpenVrDisplayPlugin::internalActivate() { _container->setIsOptionChecked(StandingHMDSensorMode, true); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index fda5e37c2a..2e31bfa2c6 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -21,6 +21,8 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + void init() override; + float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; } void customizeContext() override; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 12813dae93..c77bb66ead 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -229,7 +229,6 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); } } diff --git a/scripts/system/away.js b/scripts/system/away.js index 38b0f13c00..8f252cc449 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -158,6 +158,8 @@ function goAway() { return; } + UserActivityLogger.toggledAway(true); + isAway = true; print('going "away"'); wasMuted = AudioDevice.getMuted(); @@ -189,6 +191,9 @@ function goActive() { if (!isAway) { return; } + + UserActivityLogger.toggledAway(false); + isAway = false; print('going "active"'); if (!wasMuted) { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 42eddf11c3..c10f938bde 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -150,6 +150,8 @@ function showMarketplace(marketplaceID) { marketplaceWindow.setURL(url); marketplaceWindow.setVisible(true); marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); } function hideMarketplace() { @@ -347,6 +349,7 @@ var toolBar = (function() { selectionManager.clearSelections(); cameraManager.disable(); } else { + UserActivityLogger.enabledEdit(); hasShownPropertiesTool = false; entityListTool.setVisible(true); gridTool.setVisible(true); diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 9d33e473af..fb5ba02441 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -37,6 +37,8 @@ function showExamples(marketplaceID) { print("setting examples URL to " + url); examplesWindow.setURL(url); examplesWindow.setVisible(true); + + UserActivityLogger.openedMarketplace(); } function hideExamples() {