mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Analytics: Added an new analytics module
This commit is contained in:
parent
55a917ebe5
commit
413164f46a
60 changed files with 11508 additions and 0 deletions
67
modules/juce_analytics/analytics/juce_Analytics.cpp
Normal file
67
modules/juce_analytics/analytics/juce_Analytics.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
void Analytics::addDestination (AnalyticsDestination* destination)
|
||||
{
|
||||
destinations.add (destination);
|
||||
}
|
||||
|
||||
void Analytics::setUserId (const String& newUserId)
|
||||
{
|
||||
userId = newUserId;
|
||||
}
|
||||
|
||||
void Analytics::setUserProperties (const StringPairArray& properties)
|
||||
{
|
||||
userProperties = properties;
|
||||
}
|
||||
|
||||
void Analytics::logEvent (const String& eventName,
|
||||
const StringPairArray& parameters)
|
||||
{
|
||||
if (! isSuspended)
|
||||
{
|
||||
AnalyticsDestination::AnalyticsEvent event
|
||||
{
|
||||
eventName,
|
||||
Time::getMillisecondCounter(),
|
||||
parameters,
|
||||
userId,
|
||||
userProperties
|
||||
};
|
||||
|
||||
for (auto* destination : destinations)
|
||||
destination->logEvent (event);
|
||||
}
|
||||
}
|
||||
|
||||
void Analytics::setSuspended (bool shouldBeSuspended)
|
||||
{
|
||||
isSuspended = shouldBeSuspended;
|
||||
}
|
||||
|
||||
juce_ImplementSingleton (Analytics)
|
||||
|
||||
}
|
||||
99
modules/juce_analytics/analytics/juce_Analytics.h
Normal file
99
modules/juce_analytics/analytics/juce_Analytics.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A singleton class to manage analytics data.
|
||||
|
||||
Use an Analytics object to manage sending analytics data to one or more
|
||||
AnalyticsDestinations.
|
||||
|
||||
@see AnalyticsDestination, ThreadedAnalyticsDestination,
|
||||
AnalyticsDestination::AnalyticsEvent
|
||||
*/
|
||||
class JUCE_API Analytics : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Adds an AnalyticsDestination to the list of AnalyticsDestinations
|
||||
managed by this Analytics object.
|
||||
|
||||
The Analytics class will take ownership of the AnalyticsDestination
|
||||
passed to this function.
|
||||
|
||||
@param destination the AnalyticsDestination to manage
|
||||
*/
|
||||
void addDestination (AnalyticsDestination* destination);
|
||||
|
||||
/** Sets a user ID that will be added to all AnalyticsEvents sent to
|
||||
AnalyticsDestinations.
|
||||
|
||||
@param newUserId the userId to add to AnalyticsEvents
|
||||
*/
|
||||
void setUserId (const String& newUserId);
|
||||
|
||||
/** Sets some user properties that will be added to all AnalyticsEvents sent
|
||||
to AnalyticsDestinations.
|
||||
|
||||
@param properties the userProperties to add to AnalyticsEvents
|
||||
*/
|
||||
void setUserProperties (const StringPairArray& properties);
|
||||
|
||||
/** Sends an AnalyticsEvent to all AnalyticsDestinations.
|
||||
|
||||
The AnalyticsEvent will be timestamped, and will have the userId and
|
||||
userProperties populated by values previously set by calls to
|
||||
setUserId and setUserProperties. The name and parameters will be
|
||||
populated by the arguments supplied to this function.
|
||||
|
||||
@param eventName the event name
|
||||
@param parameters the event parameters
|
||||
*/
|
||||
void logEvent (const String& eventName, const StringPairArray& parameters);
|
||||
|
||||
/** Suspends analytics submission to AnalyticsDestinations.
|
||||
|
||||
@param shouldBeSuspended if event submission should be suspended
|
||||
*/
|
||||
void setSuspended (bool shouldBeSuspended);
|
||||
|
||||
|
||||
juce_DeclareSingleton (Analytics, true)
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Analytics() = default;
|
||||
|
||||
String userId;
|
||||
StringPairArray userProperties;
|
||||
|
||||
bool isSuspended = false;
|
||||
|
||||
OwnedArray<AnalyticsDestination> destinations;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Analytics)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
55
modules/juce_analytics/analytics/juce_ButtonTracker.cpp
Normal file
55
modules/juce_analytics/analytics/juce_ButtonTracker.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ButtonTracker::ButtonTracker (Button& buttonToTrack,
|
||||
const String& triggeredEventName,
|
||||
const StringPairArray& triggeredEventParameters)
|
||||
: button (buttonToTrack),
|
||||
eventName (triggeredEventName),
|
||||
eventParameters (triggeredEventParameters)
|
||||
{
|
||||
button.addListener (this);
|
||||
}
|
||||
|
||||
ButtonTracker::~ButtonTracker()
|
||||
{
|
||||
button.removeListener (this);
|
||||
}
|
||||
|
||||
void ButtonTracker::buttonClicked (Button* b)
|
||||
{
|
||||
if (b == &button)
|
||||
{
|
||||
auto params = eventParameters;
|
||||
|
||||
if (button.getClickingTogglesState())
|
||||
params.set ("ButtonState", button.getToggleState() ? "On" : "Off");
|
||||
|
||||
Analytics::getInstance()->logEvent (eventName, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
||||
70
modules/juce_analytics/analytics/juce_ButtonTracker.h
Normal file
70
modules/juce_analytics/analytics/juce_ButtonTracker.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class that automatically sends analytics events to the Analytics singleton
|
||||
when a button is clicked.
|
||||
|
||||
@see Analytics, AnalyticsDestination::AnalyticsEvent
|
||||
*/
|
||||
class JUCE_API ButtonTracker : private Button::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creating one of these automatically sends analytics events to the Analytics
|
||||
singeton when the corresponding button is clicked.
|
||||
|
||||
The name and parameters of the analytics event will be populated from the
|
||||
variables supplied here. If clicking changes the button's state then the
|
||||
parameters will have a {"ButtonState", "On"/"Off"} entry added.
|
||||
|
||||
@param buttonToTrack the button to track
|
||||
@param triggeredEventName the name of the generated event
|
||||
@param triggeredEventParameters the parameters to add to the generated
|
||||
event
|
||||
|
||||
@see Analytics, AnalyticsDestination::AnalyticsEvent
|
||||
*/
|
||||
ButtonTracker (Button& buttonToTrack,
|
||||
const String& triggeredEventName,
|
||||
const StringPairArray& triggeredEventParameters = {});
|
||||
|
||||
/** Destructor. */
|
||||
~ButtonTracker();
|
||||
|
||||
private:
|
||||
/** @internal */
|
||||
void buttonClicked (Button*) override;
|
||||
|
||||
Button& button;
|
||||
const String eventName;
|
||||
const StringPairArray eventParameters;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonTracker)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An interface for handling analytics events collected by an Analytics object.
|
||||
|
||||
For basic analytics logging you can implement this interface and add your
|
||||
class to an Analytics object.
|
||||
|
||||
For more advanced logging you may want to subclass
|
||||
ThreadedAnalyticsDestination instead, which is more suitable for interacting
|
||||
with web servers and other time consuming destinations.
|
||||
|
||||
@see Analytics, ThreadedAnalyticsDestination
|
||||
*/
|
||||
struct JUCE_API AnalyticsDestination
|
||||
{
|
||||
/** Contains information about an event to be logged. */
|
||||
struct AnalyticsEvent
|
||||
{
|
||||
/** The name of the event. */
|
||||
String name;
|
||||
|
||||
/**
|
||||
The timestamp of the event.
|
||||
|
||||
Timestamps are automatically applied by an Analytics object and are
|
||||
derived from Time::getMillisecondCounter(). As such these timestamps
|
||||
do not represent absolute times, but relative timings of events for
|
||||
each user in each session will be accurate.
|
||||
*/
|
||||
uint32 timestamp;
|
||||
|
||||
/** The parameters of the event. */
|
||||
StringPairArray parameters;
|
||||
|
||||
/** The user ID associated with the event. */
|
||||
String userID;
|
||||
|
||||
/** Properties associated with the user. */
|
||||
StringPairArray userProperties;
|
||||
};
|
||||
|
||||
/** Constructor. */
|
||||
AnalyticsDestination() = default;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~AnalyticsDestination() {}
|
||||
|
||||
/**
|
||||
When an AnalyticsDestination is added to an Analytics object this method
|
||||
is called when an analytics event is triggered from the Analytics
|
||||
object.
|
||||
|
||||
Override this method to log the event information somewhere useful.
|
||||
*/
|
||||
virtual void logEvent (const AnalyticsEvent& event) = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsDestination)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ThreadedAnalyticsDestination::ThreadedAnalyticsDestination (const String& threadName)
|
||||
: dispatcher (threadName, *this)
|
||||
{}
|
||||
|
||||
ThreadedAnalyticsDestination::~ThreadedAnalyticsDestination()
|
||||
{
|
||||
// If you hit this assertion then the analytics thread has not been shut down
|
||||
// before this class is destroyed. Call stopAnalyticsThread() in your destructor!
|
||||
jassert (! dispatcher.isThreadRunning());
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::setBatchPeriod (int newBatchPeriodMilliseconds)
|
||||
{
|
||||
dispatcher.batchPeriodMilliseconds = newBatchPeriodMilliseconds;
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::logEvent (const AnalyticsEvent& event)
|
||||
{
|
||||
dispatcher.addToQueue (event);
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::startAnalyticsThread (int initialBatchPeriodMilliseconds)
|
||||
{
|
||||
setBatchPeriod (initialBatchPeriodMilliseconds);
|
||||
dispatcher.startThread();
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::stopAnalyticsThread (int timeout)
|
||||
{
|
||||
dispatcher.signalThreadShouldExit();
|
||||
stopLoggingEvents();
|
||||
dispatcher.stopThread (timeout);
|
||||
|
||||
if (dispatcher.eventQueue.size() > 0)
|
||||
saveUnloggedEvents (dispatcher.eventQueue);
|
||||
}
|
||||
|
||||
ThreadedAnalyticsDestination::EventDispatcher::EventDispatcher (const String& threadName,
|
||||
ThreadedAnalyticsDestination& destination)
|
||||
: Thread (threadName),
|
||||
parent (destination)
|
||||
{}
|
||||
|
||||
void ThreadedAnalyticsDestination::EventDispatcher::run()
|
||||
{
|
||||
// We may have inserted some events into the queue (on the message thread)
|
||||
// before this thread has started, so make sure the old events are at the
|
||||
// front of the queue.
|
||||
{
|
||||
std::deque<AnalyticsEvent> restoredEventQueue;
|
||||
parent.restoreUnloggedEvents (restoredEventQueue);
|
||||
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
for (auto rit = restoredEventQueue.rbegin(); rit != restoredEventQueue.rend(); ++rit)
|
||||
eventQueue.push_front (*rit);
|
||||
}
|
||||
|
||||
const int maxBatchSize = parent.getMaximumBatchSize();
|
||||
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
auto eventsToSendCapacity = maxBatchSize - eventsToSend.size();
|
||||
|
||||
if (eventsToSendCapacity > 0)
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
const auto numEventsInQueue = (int) eventQueue.size();
|
||||
|
||||
if (numEventsInQueue > 0)
|
||||
{
|
||||
const auto numEventsToAdd = jmin (eventsToSendCapacity, numEventsInQueue);
|
||||
|
||||
for (size_t i = 0; i < (size_t) numEventsToAdd; ++i)
|
||||
eventsToSend.add (eventQueue[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const auto submissionTime = Time::getMillisecondCounter();
|
||||
|
||||
if (! eventsToSend.isEmpty())
|
||||
{
|
||||
if (parent.logBatchedEvents (eventsToSend))
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
|
||||
for (auto i = 0; i < eventsToSend.size(); ++i)
|
||||
eventQueue.pop_front();
|
||||
|
||||
eventsToSend.clearQuick();
|
||||
}
|
||||
}
|
||||
|
||||
while (Time::getMillisecondCounter() - submissionTime < (uint32) batchPeriodMilliseconds.get())
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
Thread::sleep (100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedAnalyticsDestination::EventDispatcher::addToQueue (const AnalyticsEvent& event)
|
||||
{
|
||||
const ScopedLock lock (queueAccess);
|
||||
eventQueue.push_back (event);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
namespace DestinationTestHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
struct TestDestination : public ThreadedAnalyticsDestination
|
||||
{
|
||||
TestDestination (std::deque<AnalyticsEvent>& loggedEvents,
|
||||
std::deque<AnalyticsEvent>& unloggedEvents)
|
||||
: ThreadedAnalyticsDestination ("ThreadedAnalyticsDestinationTest"),
|
||||
loggedEventQueue (loggedEvents),
|
||||
unloggedEventStore (unloggedEvents)
|
||||
{}
|
||||
|
||||
virtual ~TestDestination() {}
|
||||
|
||||
int getMaximumBatchSize() override
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
|
||||
{
|
||||
unloggedEventStore = eventsToSave;
|
||||
}
|
||||
|
||||
void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
|
||||
{
|
||||
restoredEventQueue = unloggedEventStore;
|
||||
}
|
||||
|
||||
std::deque<AnalyticsEvent>& loggedEventQueue;
|
||||
std::deque<AnalyticsEvent>& unloggedEventStore;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct BasicDestination : public TestDestination
|
||||
{
|
||||
BasicDestination (std::deque<AnalyticsEvent>& loggedEvents,
|
||||
std::deque<AnalyticsEvent>& unloggedEvents)
|
||||
: TestDestination (loggedEvents, unloggedEvents)
|
||||
{
|
||||
startAnalyticsThread (100);
|
||||
}
|
||||
|
||||
virtual ~BasicDestination()
|
||||
{
|
||||
stopAnalyticsThread (1000);
|
||||
}
|
||||
|
||||
|
||||
bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
|
||||
{
|
||||
jassert (events.size() <= getMaximumBatchSize());
|
||||
|
||||
for (auto& event : events)
|
||||
loggedEventQueue.push_back (event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopLoggingEvents() override {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct SlowWebDestination : public TestDestination
|
||||
{
|
||||
SlowWebDestination (std::deque<AnalyticsEvent>& loggedEvents,
|
||||
std::deque<AnalyticsEvent>& unloggedEvents)
|
||||
: TestDestination (loggedEvents, unloggedEvents)
|
||||
{
|
||||
startAnalyticsThread (initialPeriod);
|
||||
}
|
||||
|
||||
virtual ~SlowWebDestination()
|
||||
{
|
||||
stopAnalyticsThread (1000);
|
||||
}
|
||||
|
||||
bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
|
||||
{
|
||||
threadHasStarted.signal();
|
||||
|
||||
jassert (events.size() <= getMaximumBatchSize());
|
||||
|
||||
{
|
||||
const ScopedLock lock (webStreamCreation);
|
||||
|
||||
if (shouldExit)
|
||||
return false;
|
||||
|
||||
// An attempt to connect to an unroutable IP address will hang
|
||||
// indefinitely, which simulates a very slow server
|
||||
webStream = new WebInputStream (URL ("http://1.192.0.0"), true);
|
||||
}
|
||||
|
||||
String data;
|
||||
|
||||
for (auto& event : events)
|
||||
data << event.name;
|
||||
|
||||
webStream->withExtraHeaders (data);
|
||||
|
||||
const auto success = webStream->connect (nullptr);
|
||||
|
||||
// Exponential backoff on failure
|
||||
if (success)
|
||||
period = initialPeriod;
|
||||
else
|
||||
period *= 2;
|
||||
|
||||
setBatchPeriod (period);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void stopLoggingEvents() override
|
||||
{
|
||||
const ScopedLock lock (webStreamCreation);
|
||||
|
||||
shouldExit = true;
|
||||
|
||||
if (webStream != nullptr)
|
||||
webStream->cancel();
|
||||
}
|
||||
|
||||
const int initialPeriod = 100;
|
||||
int period = initialPeriod;
|
||||
|
||||
CriticalSection webStreamCreation;
|
||||
bool shouldExit = false;
|
||||
|
||||
ScopedPointer<WebInputStream> webStream;
|
||||
|
||||
WaitableEvent threadHasStarted;
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ThreadedAnalyticsDestinationTests : public UnitTest
|
||||
{
|
||||
ThreadedAnalyticsDestinationTests()
|
||||
: UnitTest ("ThreadedAnalyticsDestination")
|
||||
{}
|
||||
|
||||
void compareEventQueues (const std::deque<AnalyticsDestination::AnalyticsEvent>& a,
|
||||
const std::deque<AnalyticsDestination::AnalyticsEvent>& b)
|
||||
{
|
||||
const auto numEntries = a.size();
|
||||
expectEquals (b.size(), numEntries);
|
||||
|
||||
for (size_t i = 0; i < numEntries; ++i)
|
||||
{
|
||||
expectEquals (a[i].name, b[i].name);
|
||||
expect (a[i].timestamp == b[i].timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
std::deque<AnalyticsDestination::AnalyticsEvent> testEvents;
|
||||
|
||||
for (int i = 0; i < 7; ++i)
|
||||
testEvents.push_back ({ String (i), Time::getMillisecondCounter(), {}, "TestUser", {} });
|
||||
|
||||
std::deque<AnalyticsDestination::AnalyticsEvent> loggedEvents, unloggedEvents;
|
||||
|
||||
beginTest ("Basic");
|
||||
{
|
||||
DestinationTestHelpers::BasicDestination destination (loggedEvents, unloggedEvents);
|
||||
|
||||
for (auto& event : testEvents)
|
||||
destination.logEvent (event);
|
||||
|
||||
Thread::sleep (400);
|
||||
|
||||
compareEventQueues (loggedEvents, testEvents);
|
||||
expect (unloggedEvents.size() == 0);
|
||||
|
||||
loggedEvents.clear();
|
||||
}
|
||||
|
||||
beginTest ("Web");
|
||||
{
|
||||
{
|
||||
DestinationTestHelpers::SlowWebDestination destination (loggedEvents, unloggedEvents);
|
||||
|
||||
for (auto& event : testEvents)
|
||||
destination.logEvent (event);
|
||||
}
|
||||
|
||||
expect (loggedEvents.size() == 0);
|
||||
compareEventQueues (unloggedEvents, testEvents);
|
||||
|
||||
{
|
||||
DestinationTestHelpers::SlowWebDestination destination (loggedEvents, unloggedEvents);
|
||||
|
||||
destination.threadHasStarted.wait();
|
||||
unloggedEvents.clear();
|
||||
}
|
||||
|
||||
expect (loggedEvents.size() == 0);
|
||||
compareEventQueues (unloggedEvents, testEvents);
|
||||
|
||||
unloggedEvents.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadedAnalyticsDestinationTests threadedAnalyticsDestinationTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for dispatching analytics events on a dedicated thread.
|
||||
|
||||
This class is particularly useful for sending analytics events to a web
|
||||
server without blocking the message thread. It can also save (and restore)
|
||||
events that were not dispatched so no information is lost when an internet
|
||||
connection is absent or something else prevents successful logging.
|
||||
|
||||
Once startAnalyticsThread is called the logBatchedEvents method is
|
||||
periodically invoked on an analytics thread, with the period determined by
|
||||
calls to setBatchPeriod. Here events are grouped together into batches, with
|
||||
the maximum batch size set by the implementation of getMaximumBatchSize.
|
||||
|
||||
It's important to call stopAnalyticsThread in the destructor of your
|
||||
subclass (or before then) to give the analytics thread time to shut down.
|
||||
Calling stopAnalyticsThread will, in turn, call stopLoggingEvents, which
|
||||
you should use to terminate the currently running logBatchedEvents call.
|
||||
|
||||
@see Analytics, AnalyticsDestination, AnalyticsDestination::AnalyticsEvent
|
||||
*/
|
||||
class JUCE_API ThreadedAnalyticsDestination : public AnalyticsDestination
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a ThreadedAnalyticsDestination.
|
||||
|
||||
@param threadName used to identify the analytics
|
||||
thread in debug builds
|
||||
*/
|
||||
ThreadedAnalyticsDestination (const String& threadName = "Analytics thread");
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ThreadedAnalyticsDestination();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Override this method to provide the maximum batch size you can handle in
|
||||
your subclass.
|
||||
|
||||
Calls to logBatchedEvents will contain no more than this number of events.
|
||||
*/
|
||||
virtual int getMaximumBatchSize() = 0;
|
||||
|
||||
/**
|
||||
This method will be called periodically on the analytics thread.
|
||||
|
||||
If this method returns false then the subsequent call of this function will
|
||||
contain the same events as previous call, plus any new events that have been
|
||||
generated in the period between calls. The order of events will not be
|
||||
changed. This allows you to retry logging events until they are logged
|
||||
successfully.
|
||||
|
||||
@param events a list of events to be logged
|
||||
@returns if the events were successfully logged
|
||||
*/
|
||||
virtual bool logBatchedEvents (const Array<AnalyticsEvent>& events) = 0;
|
||||
|
||||
/**
|
||||
You must always call stopAnalyticsThread in the destructor of your subclass
|
||||
(or before then) to give the analytics thread time to shut down.
|
||||
|
||||
Calling stopAnalyticsThread triggers a call to this method. At this point
|
||||
you are guaranteed that logBatchedEvents has been called for the last time
|
||||
and you should make sure that the current call to logBatchedEvents finishes
|
||||
as quickly as possible. This method and a subsequent call to
|
||||
saveUnloggedEvents must both complete before the timeout supplied to
|
||||
stopAnalyticsThread.
|
||||
|
||||
In a normal use case stopLoggingEvents will be called on the message thread
|
||||
from the destructor of your ThreadedAnalyticsDestination subclass, and must
|
||||
stop the logBatchedEvents method which is running on the analytics thread.
|
||||
|
||||
@see stopAnalyticsThread
|
||||
*/
|
||||
virtual void stopLoggingEvents() = 0;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Call this to set the period between logBatchedEvents invocations.
|
||||
|
||||
This method is thread safe and can be used to implements things like
|
||||
exponential backoff in logBatchedEvents calls.
|
||||
|
||||
@param newSubmissionPeriodMilliseconds the new submission period to
|
||||
use in milliseconds
|
||||
*/
|
||||
void setBatchPeriod (int newSubmissionPeriodMilliseconds);
|
||||
|
||||
/**
|
||||
Adds an event to the queue, which will ultimately be submitted to
|
||||
logBatchedEvents.
|
||||
|
||||
This method is thread safe.
|
||||
|
||||
@param event the analytics event to add to the queue
|
||||
*/
|
||||
void logEvent (const AnalyticsEvent& event) override final;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/**
|
||||
Starts the analytics thread, with an initial event batching period.
|
||||
|
||||
@param initialBatchPeriodMilliseconds the initial event batching period
|
||||
in milliseconds
|
||||
*/
|
||||
void startAnalyticsThread (int initialBatchPeriodMilliseconds);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Triggers the shutdown of the analytics thread.
|
||||
|
||||
You must call this method in the destructor of your subclass (or before
|
||||
then) to give the analytics thread time to shut down.
|
||||
|
||||
This method invokes stopLoggingEvents and you should ensure that both the
|
||||
analytics thread and a call to saveUnloggedEvents are able to finish before
|
||||
the supplied timeout. This timeout is important because on platforms like
|
||||
iOS an app is killed if it takes too long to shut down.
|
||||
|
||||
@param timeoutMilliseconds the number of milliseconds before
|
||||
the analytics thread is forcibly
|
||||
terminated
|
||||
*/
|
||||
void stopAnalyticsThread (int timeoutMilliseconds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
/**
|
||||
This method will be called when the analytics thread is shut down,
|
||||
giving you the chance to save any analytics events that could not be
|
||||
logged. Once saved these events can be put back into the queue of events
|
||||
when the ThreadedAnalyticsDestination is recreated via
|
||||
restoreUnloggedEvents.
|
||||
|
||||
This method should return as quickly as possible, as both
|
||||
stopLoggingEvents and this method need to complete inside the timeout
|
||||
set in stopAnalyticsThread.
|
||||
|
||||
@param eventsToSave the events that could not be logged
|
||||
|
||||
@see stopAnalyticsThread, stopLoggingEvents, restoreUnloggedEvents
|
||||
*/
|
||||
virtual void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) = 0;
|
||||
|
||||
/**
|
||||
The counterpart to saveUnloggedEvents.
|
||||
|
||||
Events added to the event queue provided by this method will be the
|
||||
first events supplied to logBatchedEvents calls. Use this method to
|
||||
restore any unlogged events previously stored in a call to
|
||||
saveUnloggedEvents.
|
||||
|
||||
This method is called on the analytics thread.
|
||||
|
||||
@param restoredEventQueue place restored events into this queue
|
||||
|
||||
@see saveUnloggedEvents
|
||||
*/
|
||||
virtual void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) = 0;
|
||||
|
||||
struct EventDispatcher : public Thread
|
||||
{
|
||||
EventDispatcher (const String& threadName, ThreadedAnalyticsDestination&);
|
||||
|
||||
void run() override;
|
||||
void addToQueue (const AnalyticsEvent&);
|
||||
|
||||
ThreadedAnalyticsDestination& parent;
|
||||
|
||||
std::deque<AnalyticsEvent> eventQueue;
|
||||
CriticalSection queueAccess;
|
||||
|
||||
Atomic<int> batchPeriodMilliseconds { 1000 };
|
||||
|
||||
Array<AnalyticsEvent> eventsToSend;
|
||||
};
|
||||
|
||||
const String destinationName;
|
||||
EventDispatcher dispatcher;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadedAnalyticsDestination)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
40
modules/juce_analytics/juce_analytics.cpp
Normal file
40
modules/juce_analytics/juce_analytics.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_ANALYTICS_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#include "juce_analytics.h"
|
||||
|
||||
#include "destinations/juce_ThreadedAnalyticsDestination.cpp"
|
||||
#include "analytics/juce_Analytics.cpp"
|
||||
#include "analytics/juce_ButtonTracker.cpp"
|
||||
56
modules/juce_analytics/juce_analytics.h
Normal file
56
modules/juce_analytics/juce_analytics.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.txt file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_analytics
|
||||
vendor: juce
|
||||
version: 5.1.1
|
||||
name: JUCE analytics classes
|
||||
description: Classes to collect analytics and send to destinations
|
||||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
|
||||
dependencies: juce_gui_basics
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_ANALYTICS_H_INCLUDED
|
||||
|
||||
#include <queue>
|
||||
#include <juce_gui_basics/juce_gui_basics.h>
|
||||
|
||||
#include "destinations/juce_AnalyticsDestination.h"
|
||||
#include "destinations/juce_ThreadedAnalyticsDestination.h"
|
||||
#include "analytics/juce_Analytics.h"
|
||||
#include "analytics/juce_ButtonTracker.h"
|
||||
Loading…
Add table
Add a link
Reference in a new issue