Skip to content
Snippets Groups Projects
UsageService.cpp 9.36 KiB
Newer Older
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/UsageService.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/ChecksumHelper.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/Exception.h"
#include "MantidKernel/InternetHelper.h"
#include "MantidKernel/MantidVersion.h"
#include "MantidKernel/Logger.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidKernel/ParaViewVersion.h"

#include <Poco/ActiveResult.h>

Nick Draper's avatar
Nick Draper committed
#include <json/json.h>

namespace Mantid {
namespace Kernel {

/// static logger
Nick Draper's avatar
Nick Draper committed
Kernel::Logger g_log("UsageServiceImpl");
// const std::string STARTUP_URL("http://reports.mantidproject.org/api/usage");
// const std::string
//     STARTUP_URL(("http://ptsv2.com/t/mantidmat/post")); // dev location
Nick Draper's avatar
Nick Draper committed
// http://posttestserver.com/data/
// const std::string
// FEATURE_URL("http://reports.mantidproject.org/api/feature");
// const std::string
//     FEATURE_URL(("http://ptsv2.com/t/mantidmat/post")); // dev location
Nick Draper's avatar
Nick Draper committed

//----------------------------------------------------------------------------------------------
/** FeatureUsage
Nick Draper's avatar
Nick Draper committed
FeatureUsage::FeatureUsage(const std::string &type, const std::string &name,
                           const bool internal)
    : type(type), name(name), internal(internal) {}

// Better brute force.
bool FeatureUsage::operator<(const FeatureUsage &r) const {
  if (type < r.type)
    return true;
  if (type > r.type)
    return false;
  // Otherwise type are equal
  if (name < r.name)
    return true;
  if (name > r.name)
    return false;
  // Otherwise name are equal
  if (static_cast<int>(internal) < static_cast<int>(r.internal))
    return true;
  if (static_cast<int>(internal) > static_cast<int>(r.internal))
    return false;
  // Otherwise all are equal
  return false;
::Json::Value FeatureUsage::asJson() const {
  ::Json::Value retVal;

  retVal["type"] = type;
  retVal["name"] = name;
  retVal["internal"] = internal;

  return retVal;
Nick Draper's avatar
Nick Draper committed
}

//----------------------------------------------------------------------------------------------
Nick Draper's avatar
Nick Draper committed
/** Constructor for UsageServiceImpl
Nick Draper's avatar
Nick Draper committed
 */
Nick Draper's avatar
Nick Draper committed
UsageServiceImpl::UsageServiceImpl()
Nick Draper's avatar
Nick Draper committed
    : m_timer(), m_timerTicks(0), m_timerTicksTarget(0), m_FeatureQueue(),
      m_FeatureQueueSizeThreshold(50), m_isEnabled(false), m_mutex(),
      m_application("python"),
      m_startupActiveMethod(this, &UsageServiceImpl::sendStartupAsyncImpl),
      m_featureActiveMethod(this, &UsageServiceImpl::sendFeatureAsyncImpl) {
Nick Draper's avatar
Nick Draper committed
  setInterval(60);
  int retval = Mantid::Kernel::ConfigService::Instance().getValue(
      "usage_root_url", m_url);
  if (retval == 0) {
    g_log.error() << "Failed to load usage report url\n";
  } else {
    g_log.information() << "Root usage reporting url is " << m_url << "\n";
  }
void UsageServiceImpl::setApplication(const std::string &name) {
  m_application = name;
}

std::string UsageServiceImpl::getApplication() const { return m_application; }
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::setInterval(const uint32_t seconds) {
Nick Draper's avatar
Nick Draper committed
  // set the ticks target to by 24 hours / interval
  m_timerTicksTarget = 24 * 60 * 60 / seconds;

  m_timer.setStartInterval((seconds * 1000));
Nick Draper's avatar
Nick Draper committed
  m_timer.setPeriodicInterval((seconds * 1000));
}

Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::registerStartup() {
Nick Draper's avatar
Nick Draper committed
  if (isEnabled()) {
    sendStartupReport();
  }
}

/** registerFeatureUsage
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::registerFeatureUsage(const std::string &type,
Nick Draper's avatar
Nick Draper committed
                                            const std::string &name,
                                            const bool internal) {
  if (isEnabled()) {
    std::lock_guard<std::mutex> _lock(m_mutex);
    m_FeatureQueue.push(FeatureUsage(type, name, internal));
  }
Nick Draper's avatar
Nick Draper committed
bool UsageServiceImpl::isEnabled() const { return m_isEnabled; }
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::setEnabled(const bool enabled) {
Nick Draper's avatar
Nick Draper committed
  if (m_isEnabled != enabled) {
    if (enabled) {
Nick Draper's avatar
Nick Draper committed
      m_timer.start(Poco::TimerCallback<UsageServiceImpl>(
          *this, &UsageServiceImpl::timerCallback));
Nick Draper's avatar
Nick Draper committed
    } else {
      m_timer.stop();
    }
  }
  m_isEnabled = enabled;
}

Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::clear() {
  std::queue<FeatureUsage> empty;
  std::swap(m_FeatureQueue, empty);
}
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::flush() {
Nick Draper's avatar
Nick Draper committed
  if (isEnabled()) {
    sendFeatureUsageReport(true);
  }
}

/** getUpTime returns the time for which the mantid instance has been running
 @return time_duration The time for which mantid has been running.
*/
Types::Core::time_duration UsageServiceImpl::getUpTime() {
  return Types::Core::DateAndTime::getCurrentTime() - m_startTime;
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::shutdown() {
  try {
    // stop the timer
    setEnabled(false);
    // send any remaining feature usage records
    sendFeatureUsageReport(true);
  } catch (std::exception &ex) {
    g_log.error() << "Error during the shutdown of the UsageService. "
                  << ex.what();
  }
}

void UsageServiceImpl::sendStartupReport() {
Nick Draper's avatar
Nick Draper committed
  try {
    m_startTime = Types::Core::DateAndTime::getCurrentTime();
Nick Draper's avatar
Nick Draper committed
    std::string message = this->generateStartupMessage();
    // send the report
    Poco::ActiveResult<int> result = m_startupActiveMethod(message);
Nick Draper's avatar
Nick Draper committed
  } catch (std::exception &ex) {
    g_log.debug() << "Send startup usage failure. " << ex.what() << '\n';
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::sendFeatureUsageReport(const bool synchronous = false) {
Nick Draper's avatar
Nick Draper committed
  try {
    std::string message = this->generateFeatureUsageMessage();
    if (!message.empty()) {
      if (synchronous) {
        sendFeatureAsyncImpl(message);
      } else {
        Poco::ActiveResult<int> result = m_featureActiveMethod(message);
Nick Draper's avatar
Nick Draper committed
      }
    }

  } catch (std::exception &ex) {
    g_log.debug() << "sendFeatureUsageReport failure. " << ex.what() << '\n';
Nick Draper's avatar
Nick Draper committed
void UsageServiceImpl::timerCallback(Poco::Timer &) {
Nick Draper's avatar
Nick Draper committed
  m_timerTicks++;
  if (m_timerTicks > m_timerTicksTarget) {
    // send startup report
    sendStartupReport();
    m_timerTicks = 0;
  }

  // Check bufferlength
  if (m_FeatureQueue.size() > m_FeatureQueueSizeThreshold) {
    sendFeatureUsageReport();
  }
}

/**
* This puts together the system information for the json document.
*/
::Json::Value UsageServiceImpl::generateFeatureHeader() {
  ::Json::Value header;

  // mantid version and sha1
  header["mantidVersion"] = MantidVersion::versionShort();

  return header;
}

/**
* This puts together the system information for the json document.
*/
std::string UsageServiceImpl::generateStartupMessage() {
  ::Json::Value message;

  message["uid"] = Kernel::ChecksumHelper::md5FromString(
      ConfigService::Instance().getUsername());
  message["host"] = Kernel::ChecksumHelper::md5FromString(
      ConfigService::Instance().getComputerName());

  // os name, version, and architecture
  message["osName"] = ConfigService::Instance().getOSName();
  message["osArch"] = ConfigService::Instance().getOSArchitecture();
  message["osVersion"] = ConfigService::Instance().getOSVersion();
  message["osReadable"] = ConfigService::Instance().getOSVersionReadable();
#if defined(MAKE_VATES)
  // paraview
  message["ParaView"] = Kernel::ParaViewVersion::targetVersion();
#else
  message["ParaView"] = 0;
#endif
  // mantid version and sha1
  message["mantidVersion"] = MantidVersion::version();
  message["mantidSha1"] = MantidVersion::revisionFull();

  // mantid version and sha1
  message["dateTime"] = m_startTime.toISO8601String();
  message["application"] = m_application;
Nick Draper's avatar
Nick Draper committed

  ::Json::FastWriter writer;
  return writer.write(message);
}

Nick Draper's avatar
Nick Draper committed
std::string UsageServiceImpl::generateFeatureUsageMessage() {
  std::map<FeatureUsage, int> featureCountMap;
Nick Draper's avatar
Nick Draper committed

  if (!m_FeatureQueue.empty()) {
    // lock around emptying of the Q so any further threads have to wait
    std::lock_guard<std::mutex> _lock(m_mutex);
    // generate a map containing the counts of identical feature usage records
Nick Draper's avatar
Nick Draper committed
    while (!m_FeatureQueue.empty()) {
      auto featureUsage = m_FeatureQueue.front();
      m_FeatureQueue.pop();
      if (featureCountMap.find(featureUsage) == featureCountMap.end()) {
        featureCountMap[featureUsage] = 1;
      } else {
        featureCountMap[featureUsage]++;
      }
  if (!featureCountMap.empty()) {
    ::Json::FastWriter writer;
    ::Json::Value features;
    auto message = this->generateFeatureHeader();
    for (auto const &featureItem : featureCountMap) {
      ::Json::Value thisFeature = featureItem.first.asJson();
      thisFeature["count"] = featureItem.second;
      features.append(thisFeature);
    }
    if (!features.empty()) {
      message["features"] = features;
      return writer.write(message);
    }
Nick Draper's avatar
Nick Draper committed
  }
  return "";
}

//--------------------------------------------------------------------------------------------
/**
* Asynchronous execution
*/
Nick Draper's avatar
Nick Draper committed

/**Async method for sending startup messages
Nick Draper's avatar
Nick Draper committed
int UsageServiceImpl::sendStartupAsyncImpl(const std::string &message) {
  return this->sendReport(message, m_url + "/api/usage");
Nick Draper's avatar
Nick Draper committed
}

/**Async method for sending feature messages
Nick Draper's avatar
Nick Draper committed
int UsageServiceImpl::sendFeatureAsyncImpl(const std::string &message) {
  return this->sendReport(message, m_url + "/api/feature");
Nick Draper's avatar
Nick Draper committed
int UsageServiceImpl::sendReport(const std::string &message,
Nick Draper's avatar
Nick Draper committed
                                 const std::string &url) {
  int status = -1;
  try {
    Kernel::InternetHelper helper;
    std::stringstream responseStream;
Nick Draper's avatar
Nick Draper committed
    helper.setTimeout(20);
Nick Draper's avatar
Nick Draper committed
    helper.setBody(message);
    status = helper.sendRequest(url, responseStream);
  } catch (Mantid::Kernel::Exception::InternetError &e) {
    status = e.errorCode();
    g_log.information() << "Call to \"" << url << "\" responded with " << status
                        << "\n" << e.what() << "\n";
Nick Draper's avatar
Nick Draper committed
} // namespace Mantid