#include "analytics.h" #include "marshalvector.h" #include #include #include #include #include constexpr size_t ANALYTICS_RESERVED_SPACE = 256; Uuid::Uuid(const std::string &str) { if (str.empty()) { lower = upper = 0; return; } std::regex uuid_pattern("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" "fA-F]{4}-[0-9a-fA-F]{12}$"); if (!std::regex_match(str, uuid_pattern)) { throw std::invalid_argument("Invalid UUID format"); } std::string uuid = str; uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'), uuid.end()); lower = std::stoull(uuid.substr(0, 16), nullptr, 16); upper = std::stoull(uuid.substr(16, 16), nullptr, 16); } std::string Uuid::toString() const { std::stringstream ss; ss << std::hex << std::setw(8) << std::setfill('0') << (lower >> 32); ss << "-"; ss << std::hex << std::setw(4) << std::setfill('0') << ((lower >> 16) & 0xFFFF); ss << "-"; ss << std::hex << std::setw(4) << std::setfill('0') << (lower & 0xFFFF); ss << "-"; ss << std::hex << std::setw(4) << std::setfill('0') << (upper >> 48); ss << "-"; ss << std::hex << std::setw(12) << std::setfill('0') << (upper & 0xFFFFFFFFFFFFULL); return ss.str(); } AnalyticsObserver::AnalyticsObserver() : currentRenderCycleIndex(0), inactiveSpans(new std::vector()) { auto inactiveSpansGuard = WaitFreeGuard(inactiveSpans); inactiveSpansGuard->reserve(ANALYTICS_RESERVED_SPACE); } AnalyticsObserver::~AnalyticsObserver() { const auto swapRes = inactiveSpans.swap(nullptr); assert(swapRes.second); delete swapRes.first; } void AnalyticsObserver::trackPlayback(const Uuid &arrangementId, time_units::Seconds position) { auto it = activeSpans.find(arrangementId); if (it == activeSpans.end()) { activeSpans.emplace(arrangementId, PlaybackSpan(arrangementId, position, currentRenderCycleIndex)); return; } if (position > it->second.begin) { it->second.end = position; } it->second.renderCycleIndex = currentRenderCycleIndex; } void AnalyticsObserver::trackLooped(const Uuid &arrangementId) { auto it = activeSpans.find(arrangementId); if (it == activeSpans.end()) { return; } auto inactiveSpansGuard = WaitFreeGuard(inactiveSpans); if (inactiveSpansGuard->size() < ANALYTICS_RESERVED_SPACE) { inactiveSpansGuard->push_back(it->second); } activeSpans.erase(it); } void AnalyticsObserver::trackRenderCycleEnd() { auto inactiveSpansGuard = WaitFreeGuard(inactiveSpans); auto it = activeSpans.begin(); while (it != activeSpans.end()) { if (it->second.renderCycleIndex != currentRenderCycleIndex) { if (inactiveSpansGuard->size() < ANALYTICS_RESERVED_SPACE) { inactiveSpansGuard->push_back(it->second); } it = activeSpans.erase(it); } else { ++it; } } currentRenderCycleIndex++; } std::vector AnalyticsObserver::flushSpans() { const auto newInactiveSpans = new std::vector(); newInactiveSpans->reserve(ANALYTICS_RESERVED_SPACE); const auto swapRes = inactiveSpans.swap(newInactiveSpans); if (swapRes.second) { std::vector result(std::move(*swapRes.first)); delete swapRes.first; return result; } delete newInactiveSpans; return {}; } PlaybackSpanList AnalyticsObserver::flushSpansJs() { const auto spans = flushSpans(); emscripten::val result = emscripten::val::array(); for (const auto &span : spans) { result.call("push", span); } return result.as(); } EMSCRIPTEN_BINDINGS(analytics) { using namespace emscripten; value_object("PlaybackSpan") .field("arrangementId", &PlaybackSpan::getArrangementIdString, &PlaybackSpan::setArrangementIdString) .field("begin", &PlaybackSpan::getBeginSeconds, &PlaybackSpan::setBeginSeconds) .field("end", &PlaybackSpan::getEndSeconds, &PlaybackSpan::setEndSeconds); register_type("PlaybackSpan[]"); class_("AnalyticsObserver") .smart_ptr_constructor>( "AnalyticsObserver", &std::make_shared) .function("flushSpans", &AnalyticsObserver::flushSpansJs); } TEST_CASE("uuid", "[analytics]") { REQUIRE(Uuid("") == Uuid()); REQUIRE(Uuid("00000000-0000-0000-0000-000000000000") == Uuid()); const std::string uuid = "cb1aa6e5-ee7b-4611-9d8b-8031eb3913a0"; REQUIRE(Uuid(uuid).toString() == uuid); } TEST_CASE("analytics", "[analytics]") { AnalyticsObserver observer; observer.trackPlayback(Uuid("cb1aa6e5-ee7b-4611-9d8b-8031eb3913a0"), time_units::Seconds(1.0)); observer.trackRenderCycleEnd(); observer.trackRenderCycleEnd(); const auto spans = observer.flushSpans(); REQUIRE(spans.size() == 1); REQUIRE(spans[0].getArrangementIdString() == "cb1aa6e5-ee7b-4611-9d8b-8031eb3913a0"); REQUIRE(spans[0].getBeginSeconds() == 1.0); REQUIRE(spans[0].getEndSeconds() == 1.0); }