Merge pull request #8084 from huffman/feat/user-actions

Add client-side session id generation and new user activities
This commit is contained in:
Brad Hefta-Gaub 2016-06-22 12:36:18 -07:00 committed by GitHub
commit a48cce2975
34 changed files with 399 additions and 27 deletions

View file

@ -68,6 +68,7 @@
#include <input-plugins/InputPlugin.h>
#include <controllers/UserInputMapper.h>
#include <controllers/StateController.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <LogHandler.h>
#include <MainWindow.h>
#include <MessagesClient.h>
@ -149,6 +150,8 @@
#include "InterfaceParentFinder.h"
#include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
// 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<WindowScriptingInterface>();
DependencyManager::set<HMDScriptingInterface>();
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set<SpeechRecognizer>();
@ -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<AddressManager>();
// 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<qint64>(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<qint64>(memInfo.totalMemoryBytes);
properties["system_memory_used"] = static_cast<qint64>(memInfo.usedMemoryBytes);
properties["process_memory_used"] = static_cast<qint64>(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<BandwidthRecorder>();
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<NodeList>();
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<AvatarManager>();
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<AudioClient>().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<DialogsManager>().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<ScriptEngines>().data());
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().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<OffscreenUi>();
// Make the switch atomic from the perspective of other threads

View file

@ -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

View file

@ -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>();
accountManager->setSessionID(_sessionID);
accountManager->setSessionID(sessionID);
}
}

View file

@ -49,7 +49,6 @@ private:
DiscoverabilityManager();
Setting::Handle<int> _mode;
QString _sessionID;
QJsonObject _lastLocationObject;
};

View file

@ -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<AvatarData>();
}

View file

@ -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);

View file

@ -5,6 +5,7 @@
#include <QtGui/QSurfaceFormat>
#include <QtOpenGL/QGL>
#include <QOpenGLContext>
#include <QtCore/QRegularExpression>
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();

View file

@ -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

View file

@ -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<QNetworkReply*>(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);

View file

@ -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

View file

@ -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;

View file

@ -18,6 +18,7 @@
#include "UserActivityLogger.h"
#include <DependencyManager.h>
#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);

View file

@ -20,6 +20,7 @@
#include <QNetworkReply>
#include <SettingHandle.h>
#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);

View file

@ -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));
}

View file

@ -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 <QObject>
#include <QJsonObject>
#include <DependencyManager.h>
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

View file

@ -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 };

View file

@ -14,6 +14,9 @@
#include <QtCore/QDebug>
#include <QtCore/QPluginLoader>
#include <DependencyManager.h>
#include <UserActivityLogger.h>
#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();
}

View file

@ -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) {

View file

@ -28,6 +28,7 @@
#ifdef Q_OS_WIN
#include "CPUIdent.h"
#include <Psapi.h>
#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<PROCESS_MEMORY_COUNTERS*>(&pmc), sizeof(pmc))) {
return false;
}
info.processUsedMemoryBytes = pmc.PrivateUsage;
info.processPeakUsedMemoryBytes = pmc.PeakPagefileUsage;
return true;
#endif
return false;
}

View file

@ -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

View file

@ -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;
}

View file

@ -66,6 +66,7 @@ void SDL2Manager::init() {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
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)) {

View file

@ -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<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData);

View file

@ -58,7 +58,6 @@ bool SpacemouseManager::activate() {
if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
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<UserInputMapper>();
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>();
userInputMapper->registerDevice(instance);
UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse");
emit deviceConnected(getName());
}
//let one axis be dominant
//ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL);

View file

@ -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<ovrPerfHudMode>((currentDebugMode + 1) % ovrPerfHud_Count);

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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");
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -37,6 +37,8 @@ function showExamples(marketplaceID) {
print("setting examples URL to " + url);
examplesWindow.setURL(url);
examplesWindow.setVisible(true);
UserActivityLogger.openedMarketplace();
}
function hideExamples() {